Skip to content

Commit c844a26

Browse files
authored
jwt-claims as arguments example (#50)
* jwt-claims as arguments example * cleanup * cleanup * cleanup * cleanup * wip - JWT claims with explicit SQL predicate * chore: quick overview * remove dups * chore: cleanup * chore: add testing
1 parent 35ed095 commit c844a26

File tree

8 files changed

+359
-0
lines changed

8 files changed

+359
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Pulling field arguments from JWT claims
2+
3+
Uses a SQL predicate to limit customer rows returned from a database
4+
to those matching the regions defined in a JWT claim.
5+
6+
# Try it Out
7+
8+
Run the [sample operations](operations.graphql):
9+
10+
JWT with `regions: IN`.
11+
12+
```
13+
stepzen request -f operations.graphql --operation-name=Customers \
14+
--header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1IiwicmVnaW9ucyI6WyJJTiJdfQ.hDi3-qaIOSFKzlFvaXwSh0trXC3vjiOehSKE0OxgOdE"
15+
```
16+
17+
JWT with `regions: IN, UK`.
18+
19+
```
20+
stepzen request -f operations.graphql --operation-name=Customers \
21+
--header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1IiwicmVnaW9ucyI6WyJJTiIsIlVLIl19.CRD85IIMMwjaFebtQ_p3AjSoUM6KtH4gvjcfLQfdmjw"
22+
```
23+
24+
JWT with `regions: US, UK`.
25+
26+
```
27+
stepzen request -f operations.graphql --operation-name=Customers \
28+
--header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1IiwicmVnaW9ucyI6WyJVUyIsIlVLIl19.pf0-A6TN_hT-ldCvsZyqYGv4Twjm9s6wO1aatCjK9Aw"
29+
```
30+
31+
JWT with `regions: US, UK` and user supplied filter
32+
33+
```
34+
stepzen request -f operations.graphql --operation-name=Customers \
35+
--var f='{"city": {"eq":"London"}}' \
36+
--header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1IiwicmVnaW9ucyI6WyJVUyIsIlVLIl19.pf0-A6TN_hT-ldCvsZyqYGv4Twjm9s6wO1aatCjK9Aw"
37+
```
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
deployment:
2+
identity:
3+
keys:
4+
- algorithm: HS256
5+
key: development-only
6+
access:
7+
policies:
8+
- type: Query
9+
policyDefault:
10+
condition: false
11+
rules:
12+
- name: "jwt-control"
13+
fields: [customers]
14+
condition: "?$jwt"
15+
- name: "introspection"
16+
fields: [__schema, __type]
17+
condition: true
18+
configurationset:
19+
- configuration:
20+
name: postgresql_config
21+
uri: postgresql://postgresql.introspection.stepzen.net/introspection?user=testUserIntrospection&password=HurricaneStartingSample1934
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
schema
2+
@sdl(
3+
files: ["paging.graphql"]
4+
# visibilty controls how fields included through files in this directive
5+
# are visible outside the scope of this directive to GraphQL introspection
6+
# and field references through @materializer etc.
7+
#
8+
# types and fields are regular expressions that match type and field names.
9+
# Like field access rules if aat least one visibility pattern is present then by default
10+
# root operation type (Query, Mutation, Subscription) fields are not exposed.
11+
visibility: [{ expose: true, types: "Query", fields: ".*" }]
12+
) {
13+
query: Query
14+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
query Customers($f: CustomerFilter) {
2+
customers(filter: $f) {
3+
id
4+
name
5+
city
6+
region
7+
}
8+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
type Customer {
2+
id: ID!
3+
name: String
4+
email: String
5+
street: String
6+
city: String
7+
region: String
8+
}
9+
10+
"""
11+
`CustomerConnection` is the connection type for `Customer` pagination.
12+
"""
13+
type CustomerConnection {
14+
edges: [CustomerEdge]
15+
pageInfo: PageInfo!
16+
}
17+
18+
"""
19+
`CustomerEdge` provides access to the node and its cursor.
20+
"""
21+
type CustomerEdge {
22+
node: Customer
23+
cursor: String
24+
}
25+
26+
input StringFilter {
27+
eq: String
28+
ne: String
29+
}
30+
31+
input CustomerFilter {
32+
name: StringFilter
33+
email: StringFilter
34+
city: StringFilter
35+
}
36+
37+
type _RegionsList {
38+
regions: [String]!
39+
}
40+
41+
extend type Query {
42+
# customers is the exposed field that limits the caller to regions
43+
# based upon the regions claim in the request's JWT.
44+
customers(first: Int! = 10, filter: CustomerFilter): [Customer]
45+
@sequence(
46+
steps: [
47+
{ query: "_regions" }
48+
{
49+
query: "_customers_flatten"
50+
arguments: [
51+
{ name: "first", argument: "first" }
52+
{ name: "filter", argument: "filter" }
53+
]
54+
}
55+
]
56+
)
57+
58+
# extracts the regions visible to the request from the JWT.
59+
_regions: _RegionsList
60+
@value(
61+
script: {
62+
src: """
63+
{"regions": `$jwt`.regions }
64+
"""
65+
language: JSONATA
66+
}
67+
)
68+
69+
# this flattens the customer connection pagination structure
70+
# into a simple list of Customer objects.
71+
# This is needed as @sequence is not supported for connection types.
72+
_customers_flatten(
73+
first: Int! = 10
74+
filter: CustomerFilter
75+
regions: [String]!
76+
): [Customer] @materializer(query: "_customers { edges { node }}")
77+
78+
# Standard paginated field for a customers table in a database.
79+
# Additional regions argument that is used to limit customer
80+
# visibility based upon the 'regions' claim in a JWT.
81+
# The regions allows a list of regions and uses SQL ANY to match rows.
82+
_customers(
83+
first: Int! = 10
84+
after: String! = ""
85+
filter: CustomerFilter
86+
regions: [String]!
87+
): CustomerConnection
88+
@dbquery(
89+
type: "postgresql"
90+
schema: "public"
91+
query: """
92+
SELECT C.id, C.name, C.email, A.street, A.city, A.countryregion AS region
93+
FROM customer C, address A, customeraddress CA
94+
WHERE
95+
CA.customerid = C.id AND
96+
CA.addressid = A.id AND
97+
A.countryregion = ANY(CAST($1 AS VARCHAR ARRAY))
98+
"""
99+
configuration: "postgresql_config"
100+
)
101+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"endpoint": "api/miscellaneous"
3+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
const fs = require("fs");
2+
const path = require("node:path");
3+
const {
4+
deployAndRun,
5+
runtests,
6+
GQLHeaders,
7+
endpoint,
8+
getTestDescription,
9+
} = require("../../../tests/gqltest.js");
10+
11+
testDescription = getTestDescription("snippets", __dirname);
12+
13+
const requestsFile = path.join(path.dirname(__dirname), "operations.graphql");
14+
const requests = fs.readFileSync(requestsFile, "utf8").toString();
15+
16+
describe(testDescription, function () {
17+
// just deploy
18+
deployAndRun(__dirname, [], undefined);
19+
20+
// and then run with various JWTs
21+
runtests(
22+
"regions-in",
23+
endpoint,
24+
new GQLHeaders().withToken(
25+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1IiwicmVnaW9ucyI6WyJJTiJdfQ.hDi3-qaIOSFKzlFvaXwSh0trXC3vjiOehSKE0OxgOdE"
26+
),
27+
[
28+
{
29+
label: "customers",
30+
query: requests,
31+
operationName: "Customers",
32+
expected: {
33+
customers: [
34+
{
35+
id: "10",
36+
name: "Salma Khan ",
37+
city: "Delhi ",
38+
region: "IN ",
39+
},
40+
],
41+
},
42+
},
43+
]
44+
);
45+
runtests(
46+
"regions-in-uk",
47+
endpoint,
48+
new GQLHeaders().withToken(
49+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1IiwicmVnaW9ucyI6WyJJTiIsIlVLIl19.CRD85IIMMwjaFebtQ_p3AjSoUM6KtH4gvjcfLQfdmjw"
50+
),
51+
[
52+
{
53+
label: "customers",
54+
query: requests,
55+
operationName: "Customers",
56+
expected: {
57+
customers: [
58+
{
59+
id: "3",
60+
name: "Salim Ali ",
61+
city: "London ",
62+
region: "UK ",
63+
},
64+
{
65+
id: "4",
66+
name: "Jane Xiu ",
67+
city: "Edinburgh ",
68+
region: "UK ",
69+
},
70+
{
71+
id: "10",
72+
name: "Salma Khan ",
73+
city: "Delhi ",
74+
region: "IN ",
75+
},
76+
],
77+
},
78+
},
79+
]
80+
);
81+
runtests(
82+
"regions-us-uk",
83+
endpoint,
84+
new GQLHeaders().withToken(
85+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1IiwicmVnaW9ucyI6WyJVUyIsIlVLIl19.pf0-A6TN_hT-ldCvsZyqYGv4Twjm9s6wO1aatCjK9Aw"
86+
),
87+
[
88+
{
89+
label: "customers",
90+
query: requests,
91+
operationName: "Customers",
92+
expected: {
93+
customers: [
94+
{
95+
id: "1",
96+
name: "Lucas Bill ",
97+
city: "Boston ",
98+
region: "US ",
99+
},
100+
{
101+
id: "2",
102+
name: "Mandy Jones ",
103+
city: "Round Rock ",
104+
region: "US ",
105+
},
106+
{
107+
id: "3",
108+
name: "Salim Ali ",
109+
city: "London ",
110+
region: "UK ",
111+
},
112+
{
113+
id: "4",
114+
name: "Jane Xiu ",
115+
city: "Edinburgh ",
116+
region: "UK ",
117+
},
118+
{
119+
id: "5",
120+
name: "John Doe ",
121+
city: "Miami ",
122+
region: "US ",
123+
},
124+
{
125+
id: "6",
126+
name: "Jane Smith ",
127+
city: "San Francisco ",
128+
region: "US ",
129+
},
130+
{
131+
id: "7",
132+
name: "Sandeep Bhushan ",
133+
city: "New York ",
134+
region: "US ",
135+
},
136+
{
137+
id: "8",
138+
name: "George Han ",
139+
city: "Seattle ",
140+
region: "US ",
141+
},
142+
{
143+
id: "9",
144+
name: "Asha Kumari ",
145+
city: "Chicago ",
146+
region: "US ",
147+
},
148+
],
149+
},
150+
},
151+
{
152+
label: "customers-filter",
153+
query: requests,
154+
operationName: "Customers",
155+
variables: {
156+
f: { city: { eq: "London" } },
157+
},
158+
expected: {
159+
customers: [
160+
{
161+
id: "3",
162+
name: "Salim Ali ",
163+
city: "London ",
164+
region: "UK ",
165+
},
166+
],
167+
},
168+
},
169+
]
170+
);
171+
});

tests/gqltest.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const path = require("node:path");
55

66
const {
77
runtests,
8+
GQLHeaders,
89
} = require('gqltest/packages/gqltest/gqltest.js');
910

1011
const stepzen = require("gqltest/packages/gqltest/stepzen.js");
@@ -59,5 +60,8 @@ function getTestDescription(testRoot, fullDirName) {
5960

6061
exports.deployAndRun = deployAndRun;
6162
exports.getTestDescription = getTestDescription;
63+
exports.endpoint = endpoint;
6264

65+
exports.GQLHeaders = GQLHeaders;
66+
exports.runtests = runtests;
6367
exports.stepzen = stepzen;

0 commit comments

Comments
 (0)