Skip to content

Commit 1774d3a

Browse files
committed
Add at least N operator and checkExpired flag to GC Passport validation strategy
1 parent 1af7f4e commit 1774d3a

File tree

4 files changed

+70
-9
lines changed

4 files changed

+70
-9
lines changed

src/validations/passport-gated/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Before using this code, you need to create an API Key and Scorer ID to interact
2020

2121
## Stamps Metadata
2222

23-
The Stamps currently supported by Gitcoin Passport are stored in [stampsMetadata.json](./stampsMetadata.json). The Passport API has an [endpoint](https://docs.passport.gitcoin.co/building-with-passport/scorer-api/endpoint-definition#get-stamps-metadata-beta) where you can fetch all this information, but we don't do this programmatically in order to minimize the number of requests made by the validation strategy and meet the requirements listed in the main [README](../../../README.md).
23+
The Stamps currently supported by Gitcoin Passport are stored in [stampsMetadata.json](./stampsMetadata.json). The Passport API has an [endpoint](https://docs.passport.gitcoin.co/building-with-passport/scorer-api/endpoint-definition#get-stamps-metadata-beta) where you can fetch all this information, but we don't do this programmatically in order to minimize the number of requests made by the validation strategy and meet the requirements listed in the main [README](../../../README.md).
2424

2525
**NOTICE**: this file might need to be updated from time to time when Passport updates their supported Stamps and VCs.
2626

@@ -38,7 +38,7 @@ The main function (validate()) first fetches the following parameters:
3838

3939
Then, it calls the following validation methods:
4040

41-
* `validateStamps`: it uses the API to fetch the current user's Passport stamps and verifies that each has valid issuance and isn't expired. Then, depending on the `operator`, it will iterate through the required `stamps` and check that the user holds at least one verifiable credential that makes the passport eligible for that stamp. Finally, a Passport will be set as valid if it meets the criteria.
41+
* `validateStamps`: it uses the API to fetch the current user's Passport stamps and verifies that each has valid issuance and isn't expired (if checkExpired param is set to true). Then, depending on the `operator`, it will iterate through the required `stamps` and check that the user holds at least one verifiable credential that makes the passport eligible for that stamp. Finally, a Passport will be set as valid if it meets the criteria.
4242
* `validatePassportScore`: if `scoreThreshold` is set to zero this function will be omitted. Otherwise when called, it uses the Scorer API to submit the passport for scoring and get the latest score. If the API response returns a payload with `status === 'DONE'` it will return the result of evaluating the scoring threshold criteria, otherwise the implementation will make periodic requests (up to `PASSPORT_SCORER_MAX_ATTEMPTS`) to the Scorer API until getting a `DONE` status.
4343

4444
Finally, it checks the results of both eval functions and returns a boolean value indicating whether the user has a valid Passport.

src/validations/passport-gated/examples.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,20 @@
3535
"operator": "OR"
3636
},
3737
"valid": false
38+
},
39+
{
40+
"name": "Example of a passport gated validation",
41+
"author": "0x24F15402C6Bb870554489b2fd2049A85d75B982f",
42+
"space": "fabien.eth",
43+
"network": "1",
44+
"snapshot": "latest",
45+
"params": {
46+
"scoreThreshold": 20,
47+
"stamps": ["Ens", "Github", "Snapshot"],
48+
"operator": ">=N",
49+
"minStamps": 1,
50+
"checkExpired": false
51+
},
52+
"valid": true
3853
}
3954
]

src/validations/passport-gated/index.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,27 @@ const stampCredentials = STAMPS.map((stamp) => {
3636
// Useful to get stamp metadata and update `stampsMetata.json`
3737
// console.log('stampCredentials', JSON.stringify(stampCredentials.map((s) => ({"const": s.id, title: s.name}))));
3838

39-
function hasValidIssuanceAndExpiration(credential: any, proposalTs: string) {
39+
function hasValidIssuanceAndExpiration(
40+
credential: any,
41+
proposalTs: string,
42+
checkExpired: boolean
43+
) {
4044
const issuanceDate = Number(
4145
new Date(credential.issuanceDate).getTime() / 1000
4246
).toFixed(0);
4347
const expirationDate = Number(
4448
new Date(credential.expirationDate).getTime() / 1000
4549
).toFixed(0);
46-
if (issuanceDate <= proposalTs && expirationDate >= proposalTs) {
47-
return true;
50+
51+
let isValid = false;
52+
if (issuanceDate <= proposalTs) {
53+
if (checkExpired && expirationDate >= proposalTs) {
54+
isValid = true;
55+
} else if (~checkExpired) {
56+
isValid = true;
57+
}
4858
}
49-
return false;
59+
return isValid;
5060
}
5161

5262
function hasStampCredential(stampId: string, credentials: Array<string>) {
@@ -64,7 +74,9 @@ async function validateStamps(
6474
currentAddress: string,
6575
operator: string,
6676
proposalTs: string,
67-
requiredStamps: Array<string> = []
77+
requiredStamps: Array<string> = [],
78+
minStamps: number,
79+
checkExpired: boolean
6880
): Promise<boolean> {
6981
if (requiredStamps.length === 0) return true;
7082

@@ -82,7 +94,7 @@ async function validateStamps(
8294
// check expiration for all stamps
8395
const validStamps = stampsData.items
8496
.filter((stamp: any) =>
85-
hasValidIssuanceAndExpiration(stamp.credential, proposalTs)
97+
hasValidIssuanceAndExpiration(stamp.credential, proposalTs, checkExpired)
8698
)
8799
.map((stamp: any) => stamp.credential.credentialSubject.provider);
88100

@@ -94,7 +106,19 @@ async function validateStamps(
94106
return requiredStamps.some((stampId) =>
95107
hasStampCredential(stampId, validStamps)
96108
);
109+
} else if (operator === '>=N') {
110+
if (minStamps === null) {
111+
throw new Error(
112+
'When using >=N operator, minStamps parameter must be specified'
113+
);
114+
}
115+
return (
116+
requiredStamps.filter((stampId) =>
117+
hasStampCredential(stampId, validStamps)
118+
).length >= minStamps
119+
);
97120
}
121+
98122
return false;
99123
}
100124

@@ -168,6 +192,8 @@ export default class extends Validation {
168192
const requiredStamps = this.params.stamps || [];
169193
const operator = this.params.operator;
170194
const scoreThreshold = this.params.scoreThreshold;
195+
const minStamps = this.params.minStamps;
196+
const checkExpired = this.params.checkExpired;
171197

172198
if (scoreThreshold === undefined)
173199
throw new Error('Score threshold is required');
@@ -180,7 +206,9 @@ export default class extends Validation {
180206
currentAddress,
181207
operator,
182208
proposalTs,
183-
requiredStamps
209+
requiredStamps,
210+
minStamps,
211+
checkExpired
184212
);
185213

186214
if (scoreThreshold === 0) {

src/validations/passport-gated/schema.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,27 @@
3030
{
3131
"const": "OR",
3232
"title": "Require at least one stamp"
33+
},
34+
{
35+
"const": ">=N",
36+
"title": "Require at least N stamps (specified via minStamps param)"
3337
}
3438
]
3539
},
40+
"minStamps": {
41+
"type": "number",
42+
"title": "Minimum matching stamps for >=N operator",
43+
"description": "Minimum number of matching stamps for >=N operator - ignored for other operators",
44+
"minimum": 1,
45+
"maximum": 1000000,
46+
"default": null
47+
},
48+
"checkExpired": {
49+
"type": "boolean",
50+
"title": "Check stamp expiration",
51+
"description": "Whether or not to check for expired stamps - if false, expired stamps count as valid",
52+
"default": true
53+
},
3654
"stamps": {
3755
"type": "array",
3856
"title": "Stamps",

0 commit comments

Comments
 (0)