143 lines
6.0 KiB
TypeScript
143 lines
6.0 KiB
TypeScript
// Follow this setup guide to integrate the Deno language server with your editor:
|
|
// https://deno.land/manual/getting_started/setup_your_environment
|
|
// This enables autocomplete, go to definition, etc.
|
|
// Setup type definitions for built-in Supabase Runtime APIs
|
|
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
import { serve } from "https://deno.land/std@0.177.0/http/server.ts";
|
|
const AWS_REGION = Deno.env.get('AWS_REGION');
|
|
const AWS_ACCESS_KEY = Deno.env.get('AWS_ACCESS_KEY');
|
|
const AWS_SECRET_KEY = Deno.env.get('AWS_SECRET_KEY');
|
|
serve(async (req)=>{
|
|
console.log('AWS_REGION:', AWS_REGION);
|
|
console.log('AWS_ACCESS_KEY:', AWS_ACCESS_KEY?.slice(0, 5)); // for security, partial only
|
|
console.log('AWS_SECRET_KEY:', AWS_SECRET_KEY?.slice(0, 5)); // for security, partial only
|
|
try {
|
|
// Check if we have AWS credentials
|
|
if (!AWS_REGION || !AWS_ACCESS_KEY || !AWS_SECRET_KEY) {
|
|
return new Response(JSON.stringify({
|
|
error: 'AWS credentials are not configured'
|
|
}), {
|
|
status: 500,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
}
|
|
// Parse the multipart form data to get the image
|
|
const formData = await req.formData();
|
|
const image = formData.get('image');
|
|
if (!image || !(image instanceof File)) {
|
|
return new Response(JSON.stringify({
|
|
error: 'Image file is required'
|
|
}), {
|
|
status: 400,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
}
|
|
// Convert image to base64
|
|
const imageBuffer = await image.arrayBuffer();
|
|
const base64Image = btoa(String.fromCharCode(...new Uint8Array(imageBuffer)));
|
|
// Create AWS signature for authorization
|
|
const date = new Date();
|
|
const amzDate = date.toISOString().replace(/[:-]|\.\d{3}/g, '');
|
|
const dateStamp = amzDate.substring(0, 8);
|
|
const host = `rekognition.${AWS_REGION}.amazonaws.com`;
|
|
const endpoint = `https://${host}/`;
|
|
const request = {
|
|
"Image": {
|
|
"Bytes": base64Image
|
|
},
|
|
"Attributes": [
|
|
"ALL"
|
|
]
|
|
};
|
|
// AWS Signature V4 calculation
|
|
const method = 'POST';
|
|
const service = 'rekognition';
|
|
const contentType = 'application/x-amz-json-1.1';
|
|
const amzTarget = 'RekognitionService.DetectFaces';
|
|
const canonicalUri = '/';
|
|
const canonicalQueryString = '';
|
|
const payloadHash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify(request))).then((hash)=>Array.from(new Uint8Array(hash)).map((b)=>b.toString(16).padStart(2, '0')).join(''));
|
|
const canonicalHeaders = `content-type:${contentType}\n` + `host:${host}\n` + `x-amz-date:${amzDate}\n` + `x-amz-target:${amzTarget}\n`;
|
|
const signedHeaders = 'content-type;host;x-amz-date;x-amz-target';
|
|
const canonicalRequest = `${method}\n${canonicalUri}\n${canonicalQueryString}\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`;
|
|
const algorithm = 'AWS4-HMAC-SHA256';
|
|
const credentialScope = `${dateStamp}/${AWS_REGION}/${service}/aws4_request`;
|
|
const stringToSign = `${algorithm}\n${amzDate}\n${credentialScope}\n${await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalRequest)).then((hash)=>Array.from(new Uint8Array(hash)).map((b)=>b.toString(16).padStart(2, '0')).join(''))}`;
|
|
const getSignatureKey = async (key, dateStamp, regionName, serviceName)=>{
|
|
const kDate = await crypto.subtle.importKey("raw", new TextEncoder().encode(`AWS4${key}`), {
|
|
name: "HMAC",
|
|
hash: "SHA-256"
|
|
}, false, [
|
|
"sign"
|
|
]);
|
|
const kRegion = await crypto.subtle.sign("HMAC", kDate, new TextEncoder().encode(regionName));
|
|
const kService = await crypto.subtle.sign("HMAC", await crypto.subtle.importKey("raw", kRegion, {
|
|
name: "HMAC",
|
|
hash: "SHA-256"
|
|
}, false, [
|
|
"sign"
|
|
]), new TextEncoder().encode(serviceName));
|
|
return crypto.subtle.sign("HMAC", await crypto.subtle.importKey("raw", kService, {
|
|
name: "HMAC",
|
|
hash: "SHA-256"
|
|
}, false, [
|
|
"sign"
|
|
]), new TextEncoder().encode("aws4_request"));
|
|
};
|
|
const signingKey = await getSignatureKey(AWS_SECRET_KEY, dateStamp, AWS_REGION, service);
|
|
const signature = await crypto.subtle.sign("HMAC", await crypto.subtle.importKey("raw", signingKey, {
|
|
name: "HMAC",
|
|
hash: "SHA-256"
|
|
}, false, [
|
|
"sign"
|
|
]), new TextEncoder().encode(stringToSign)).then((hash)=>Array.from(new Uint8Array(hash)).map((b)=>b.toString(16).padStart(2, '0')).join(''));
|
|
const authHeader = `${algorithm} ` + `Credential=${AWS_ACCESS_KEY}/${credentialScope}, ` + `SignedHeaders=${signedHeaders}, ` + `Signature=${signature}`;
|
|
// Make request to AWS Rekognition
|
|
const response = await fetch(endpoint, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': contentType,
|
|
'X-Amz-Date': amzDate,
|
|
'X-Amz-Target': amzTarget,
|
|
'Authorization': authHeader
|
|
},
|
|
body: JSON.stringify(request)
|
|
});
|
|
const data = await response.json();
|
|
return new Response(JSON.stringify({
|
|
success: true,
|
|
faceDetails: data.FaceDetails || [],
|
|
count: (data.FaceDetails || []).length
|
|
}), {
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error("Error in detect-face function:", error);
|
|
return new Response(JSON.stringify({
|
|
error: "Failed to process the image",
|
|
details: error instanceof Error ? error.message : String(error)
|
|
}), {
|
|
status: 500,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
}
|
|
}); /* To invoke locally:
|
|
|
|
1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start)
|
|
2. Make an HTTP request:
|
|
|
|
curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/detect-face' \
|
|
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \
|
|
--header 'Content-Type: application/json' \
|
|
--data '{"name":"Functions"}'
|
|
|
|
*/
|