Skip to content

Commit

Permalink
Merge branch 'main' into ivan/insensitive
Browse files Browse the repository at this point in the history
  • Loading branch information
ichub authored Jul 17, 2024
2 parents c751a64 + f43d4ed commit 62692fd
Show file tree
Hide file tree
Showing 9 changed files with 428 additions and 138 deletions.
4 changes: 3 additions & 1 deletion apps/consumer-client/src/pages/examples/zuauth/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export default function ZuAuth(): JSX.Element {
});

if (result.type === "pcd") {
setAuthenticated(await serverLogin(result.pcdStr, config));
setAuthenticated(
await serverLogin(result.pcdStr, config, fieldsToReveal)
);
}
})();
}, [fieldsToReveal, configString]);
Expand Down
6 changes: 4 additions & 2 deletions apps/consumer-client/src/pages/examples/zuauth/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ITicketData } from "@pcd/eddsa-ticket-pcd";
import { PipelineEdDSATicketZuAuthConfig } from "@pcd/passport-interface";
import { EdDSATicketFieldsToReveal } from "@pcd/zk-eddsa-event-ticket-pcd";
import urlJoin from "url-join";
import { CONSUMER_SERVER_URL } from "../../../constants";

Expand Down Expand Up @@ -33,7 +34,8 @@ export async function logout(): Promise<void> {
*/
export async function serverLogin(
serialized: string,
config: PipelineEdDSATicketZuAuthConfig[]
config: PipelineEdDSATicketZuAuthConfig[],
fieldsToReveal: EdDSATicketFieldsToReveal
): Promise<Partial<ITicketData>> {
const response = await fetch(urlJoin(CONSUMER_SERVER_URL, `auth/login`), {
method: "POST",
Expand All @@ -43,7 +45,7 @@ export async function serverLogin(
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json"
},
body: JSON.stringify({ pcd: serialized, config })
body: JSON.stringify({ pcd: serialized, config, fieldsToReveal })
});

return await response.json();
Expand Down
10 changes: 5 additions & 5 deletions apps/consumer-server/src/routing/routes/zuauth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ export function login(
return;
}

const pcd = await authenticate(
req.body.pcd,
session.watermark,
req.body.config
);
const pcd = await authenticate(req.body.pcd, {
watermark: session.watermark,
config: req.body.config,
fieldsToReveal: req.body.fieldsToReveal
});

session.ticket = pcd.claim.partialTicket;

Expand Down
11 changes: 10 additions & 1 deletion examples/zuauth/src/app/api/login/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@ export async function POST(req: NextRequest) {

try {
const session = await getIronSession<SessionData>(cookieStore, ironOptions);
const pcd = await authenticate(body.pcd, session.watermark ?? "", config);
const pcd = await authenticate(body.pcd, {
watermark: session.watermark ?? "",
config,
fieldsToReveal: {
revealAttendeeEmail: true,
revealAttendeeName: true,
revealEventId: true,
revealProductId: true
}
});

session.user = pcd.claim.partialTicket;
await session.save();
Expand Down
4 changes: 3 additions & 1 deletion examples/zuauth/src/app/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ export default function Home() {
zupassUrl: process.env.NEXT_PUBLIC_ZUPASS_SERVER_URL as string,
fieldsToReveal: {
revealAttendeeEmail: true,
revealAttendeeName: true
revealAttendeeName: true,
revealEventId: true,
revealProductId: true
},
watermark,
config: config
Expand Down
2 changes: 2 additions & 0 deletions packages/lib/zuauth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@
"@pcd/tsconfig": "0.11.1",
"@semaphore-protocol/identity": "^3.15.2",
"@types/chai": "^4.3.5",
"@types/chai-as-promised": "^7.1.5",
"@types/mocha": "^10.0.1",
"@types/react": "^18.2.0",
"chai-as-promised": "^7.1.1",
"eslint": "^8.57.0",
"mocha": "^10.2.0",
"ts-mocha": "^10.0.0",
Expand Down
152 changes: 124 additions & 28 deletions packages/lib/zuauth/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,73 @@
import { isEqualEdDSAPublicKey } from "@pcd/eddsa-pcd";
import { PipelineEdDSATicketZuAuthConfig } from "@pcd/passport-interface";
import { ITicketData } from "@pcd/eddsa-ticket-pcd";
import { PipelineZuAuthConfig } from "@pcd/passport-interface";
import {
EdDSATicketFieldsToReveal,
ZKEdDSAEventTicketPCD,
ZKEdDSAEventTicketPCDClaim,
ZKEdDSAEventTicketPCDPackage,
ZKEdDSAEventTicketPCDTypeName
} from "@pcd/zk-eddsa-event-ticket-pcd";
import { ZuAuthArgs } from ".";

/**
* Check if a given field is defined.
*/
function checkIsDefined<T>(
field: T | undefined,
fieldName: string
): field is T {
if (field === undefined || field === null) {
throw new Error(
`Field "${fieldName}" is undefined but should have a value`
);
}
return true;
}

/**
* Check if a given field is undefined.
*/
function checkIsUndefined(field: unknown, fieldName: string): boolean {
if (field !== undefined) {
throw new Error(
`Field "${fieldName}" is defined but should not have a value`
);
}
return true;
}

/**
* Check if an individual configuration matches the claim from the PCD.
*/
function claimMatchesConfiguration(
claim: ZKEdDSAEventTicketPCDClaim,
config: PipelineZuAuthConfig
): boolean {
return (
isEqualEdDSAPublicKey(claim.signer, config.publicKey) &&
claim.partialTicket.eventId === config.eventId &&
(config.productId === undefined ||
claim.partialTicket.productId === config.productId)
);
}

const revealedFields: Record<
keyof EdDSATicketFieldsToReveal,
keyof ITicketData
> = {
revealAttendeeEmail: "attendeeEmail",
revealAttendeeName: "attendeeName",
revealAttendeeSemaphoreId: "attendeeSemaphoreId",
revealEventId: "eventId",
revealIsConsumed: "isConsumed",
revealIsRevoked: "isRevoked",
revealProductId: "productId",
revealTicketCategory: "ticketCategory",
revealTicketId: "ticketId",
revealTimestampConsumed: "timestampConsumed",
revealTimestampSigned: "timestampSigned"
} as const;

/**
* Authenticates a ticket PCD.
Expand All @@ -20,9 +83,11 @@ import {
*/
export async function authenticate(
pcdStr: string,
watermark: string,
config: PipelineEdDSATicketZuAuthConfig[]
{ watermark, config, fieldsToReveal, externalNullifier }: ZuAuthArgs
): Promise<ZKEdDSAEventTicketPCD> {
/**
* Check to see if our inputs are valid, beginning with the PCD.
*/
const serializedPCD = JSON.parse(pcdStr);
if (serializedPCD.type !== ZKEdDSAEventTicketPCDTypeName) {
throw new Error("PCD is malformed or of the incorrect type");
Expand All @@ -34,37 +99,68 @@ export async function authenticate(
throw new Error("ZK ticket PCD is not valid");
}

if (pcd.claim.watermark.toString() !== watermark) {
throw new Error("PCD watermark doesn't match");
/**
* The configuration array must not be empty.
*/
if (config.length === 0) {
throw new Error("Configuration is empty");
}

const publicKeys = config.map((em) => em.publicKey);
const productIds = new Set(
// Product ID is optional, so it's important to filter out undefined values
config
.map((em) => em.productId)
.filter((productId) => productId !== undefined)
);

if (
publicKeys.length > 0 &&
!publicKeys.find((pubKey) =>
isEqualEdDSAPublicKey(pubKey, pcd.claim.signer)
)
) {
/**
* Check if the external nullifier matches the configuration.
*/
if (externalNullifier !== undefined) {
if (pcd.claim.externalNullifier === undefined) {
throw new Error(
"PCD is missing external nullifier when one was provided"
);
}
if (
pcd.claim.externalNullifier.toString() !== externalNullifier.toString()
) {
throw new Error("External nullifier does not match the provided value");
}
} else if (pcd.claim.externalNullifier !== undefined) {
throw new Error(
"Signing key does not match any of the configured public keys"
"PCD contains an external nullifier when none was provided"
);
}

if (
productIds.size > 0 &&
pcd.claim.partialTicket.productId &&
!productIds.has(pcd.claim.partialTicket.productId)
) {
throw new Error(
"Product ID does not match any of the configured product IDs"
);
if (pcd.claim.watermark !== watermark.toString()) {
throw new Error("PCD watermark does not match");
}

checkIsUndefined(pcd.claim.validEventIds, "validEventIds");

/**
* Check that the revealed fields in the PCD match the expectations set out
* in {@link revealedFields}. This is to ensure the consistency between the
* configuration passed to this function, and the configuration used on the
* client-side when generating the PCD.
*/
for (const [revealedField, fieldName] of Object.entries(revealedFields)) {
if (fieldsToReveal[revealedField as keyof EdDSATicketFieldsToReveal]) {
checkIsDefined(pcd.claim.partialTicket[fieldName], fieldName);
} else {
checkIsUndefined(pcd.claim.partialTicket[fieldName], fieldName);
}
}

/**
* Our inputs are formally valid. Now we check to see if any of the
* configuration patterns match the claim in the PCD.
*/
let match = false;

for (const em of config) {
if (claimMatchesConfiguration(pcd.claim, em)) {
match = true;
break;
}
}

if (!match) {
throw new Error("PCD does not match any of the configured patterns");
}

return pcd;
Expand Down
15 changes: 12 additions & 3 deletions packages/lib/zuauth/src/zuauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function zuAuthRedirect(args: ZuAuthRedirectArgs): void {
*/
export function constructZkTicketProofUrl(zuAuthArgs: ZuAuthArgs): string {
const {
zupassUrl = "https://zupass.org",
zupassUrl = "https://zupass.org/",
returnUrl,
fieldsToReveal,
watermark,
Expand Down Expand Up @@ -114,6 +114,16 @@ export function constructZkTicketProofUrl(zuAuthArgs: ZuAuthArgs): string {
publicKeys.push(em.publicKey);
}

if (!fieldsToReveal.revealEventId) {
throw new Error("The event ID must be revealed for authentication");
}

if (productIds.length > 0 && !fieldsToReveal.revealProductId) {
throw new Error(
"When product IDs are specified for authentication, the product ID field must be revealed"
);
}

const args: ZKEdDSAEventTicketPCDArgs = {
ticket: {
argumentType: ArgumentTypeName.PCD,
Expand All @@ -135,8 +145,7 @@ export function constructZkTicketProofUrl(zuAuthArgs: ZuAuthArgs): string {
},
validEventIds: {
argumentType: ArgumentTypeName.StringArray,
value:
eventIds.length !== 0 && eventIds.length <= 20 ? eventIds : undefined,
value: undefined,
userProvided: false
},
fieldsToReveal: {
Expand Down
Loading

0 comments on commit 62692fd

Please sign in to comment.