Skip to content

Commit

Permalink
feat: check headers only
Browse files Browse the repository at this point in the history
when calling the checkd service worker from other workers, the request object must be cloned. by passing only headers instead, we improve performance and reduce complexity
  • Loading branch information
willswire committed Aug 31, 2024
1 parent 8c0e6bf commit 924d750
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 41 deletions.
2 changes: 1 addition & 1 deletion examples/worker/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export default {
async fetch(request, env, ctx): Promise<Response> {
const isValid = await env.CHECKD.check(request);
const isValid = await env.CHECKD.check(request.headers);
if (isValid) {
return new Response('Device Check Succeeded!');
} else {
Expand Down
70 changes: 30 additions & 40 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { WorkerEntrypoint } from 'cloudflare:workers';
import * as jose from 'jose';

// Constant for JWT algorithm
const ALGORITHM = 'ES256';

// Define the claim interface extending JWTPayload
interface DeviceClaim extends jose.JWTPayload {
device_token: string;
Expand All @@ -20,43 +17,43 @@ export default class extends WorkerEntrypoint<Env> {

/**
* Method to check the device token by calling the upstream Apple endpoint
* @param request - The incoming request object
* @param headers - The request headers object
* @returns A promise that resolves to a boolean indicating the success of the check
*/
async check(request: Request): Promise<boolean> {
async check(headers: Headers): Promise<boolean> {
try {
// Retrieve device token and environment type from the request headers
const deviceToken: string = this.getDeviceToken(request);
const isDevelopment: boolean = this.isDevelopmentEnvironment(request);
const deviceToken = this.getDeviceToken(headers);
const isDevelopment = this.isDevelopmentEnvironment(headers);

// Create the claim object for the JWT
const claim: DeviceClaim = this.createClaim(deviceToken);
const claim = this.createClaim(deviceToken);

// Generate the JWT using the claim
const jwt: string = await this.generateJWT(claim);
const jwt = await this.generateJWT(claim);

// Get the upstream endpoint based on the environment type
const upstreamEndpoint: string = this.getUpstreamEndpoint(isDevelopment);
const upstreamEndpoint = this.getUpstreamEndpoint(isDevelopment);

// Send the claim to the upstream endpoint and retrieve the response
const upstreamResponse: Response = await this.sendUpstreamRequest(upstreamEndpoint, jwt, claim);
const upstreamResponse = await this.sendUpstreamRequest(upstreamEndpoint, jwt, claim);

// Handle the upstream response and return the result
return this.handleUpstreamResponse(upstreamResponse);
// Check if the upstream response status is 200 (OK)
return upstreamResponse.status === 200;
} catch (error) {
// Return false in the event of an exception
console.error('Error during device check:', error);
return false;
}
}

/**
* Retrieves the device token from the request headers
* @param request - The incoming request object
* @param headers - The request headers object
* @returns The device token as a string
* @throws Error if the device token is missing
*/
private getDeviceToken(request: Request): string {
const deviceToken = request.headers.get('X-Apple-Device-Token');
private getDeviceToken(headers: Headers): string {
const deviceToken = headers.get('X-Apple-Device-Token');
if (!deviceToken) {
throw new Error('Device token is missing');
}
Expand All @@ -65,11 +62,11 @@ export default class extends WorkerEntrypoint<Env> {

/**
* Determines if the environment is a development environment
* @param request - The incoming request object
* @param headers - The request headers object
* @returns A boolean indicating if the environment is for development
*/
private isDevelopmentEnvironment(request: Request): boolean {
return request.headers.get('X-Apple-Device-Development') === 'true';
private isDevelopmentEnvironment(headers: Headers): boolean {
return headers.get('X-Apple-Device-Development') === 'true';
}

/**
Expand All @@ -78,10 +75,11 @@ export default class extends WorkerEntrypoint<Env> {
* @returns The claim object containing device_token, transaction_id, and timestamp
*/
private createClaim(deviceToken: string): DeviceClaim {
const currentTimestamp = Date.now();
return {
device_token: deviceToken,
transaction_id: `trns-${Date.now()}`,
timestamp: Date.now(),
transaction_id: `trns-${currentTimestamp}`,
timestamp: currentTimestamp,
};
}

Expand All @@ -91,11 +89,11 @@ export default class extends WorkerEntrypoint<Env> {
* @returns A promise that resolves to the generated JWT string
*/
private async generateJWT(claim: DeviceClaim): Promise<string> {
const kid: string = this.env.APPLE_KEY_ID;
const privateKey = await jose.importPKCS8(this.env.APPLE_PRIVATE_KEY, ALGORITHM);
const kid = this.env.APPLE_KEY_ID;
const privateKey = await jose.importPKCS8(this.env.APPLE_PRIVATE_KEY, 'ES256');

return new jose.SignJWT(claim)
.setProtectedHeader({ alg: ALGORITHM, kid })
.setProtectedHeader({ alg: 'ES256', kid })
.setIssuedAt()
.setIssuer(this.env.APPLE_DEVELOPER_ID)
.setExpirationTime('1h')
Expand All @@ -120,28 +118,20 @@ export default class extends WorkerEntrypoint<Env> {
* @returns A promise that resolves to the upstream response object
*/
private async sendUpstreamRequest(upstreamEndpoint: string, jwt: string, claim: DeviceClaim): Promise<Response> {
return fetch(upstreamEndpoint, {
const requestInit: RequestInit = {
method: 'POST',
body: JSON.stringify(claim),
headers: {
Authorization: `Bearer ${jwt}`,
'Content-Type': 'application/json',
},
});
}
};

/**
* Handles the upstream response
* @param upstreamResponse - The upstream response object
* @returns A promise that resolves to a boolean indicating the success of the operation
* @throws Error if the upstream response is not successful
*/
private async handleUpstreamResponse(upstreamResponse: Response): Promise<boolean> {
if (upstreamResponse.status !== 200) {
const errorText = await upstreamResponse.text();
throw new Error(`Upstream response error: ${upstreamResponse.status} - ${errorText}`);
try {
return await fetch(upstreamEndpoint, requestInit);
} catch (error) {
console.error('Failed to send request to upstream endpoint:', error);
throw new Error('Failed to send request to upstream');
}

return true;
}
}

0 comments on commit 924d750

Please sign in to comment.