-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactor(api): use `fetch` instead of `axios` * refactor: add environment variables type definitions * refactor(api): extract message strings As preparation for internationalization. * refactor(api): use `params` instead of `queryParams` Simplify route handling by directly integrating parameters into the route structure. * refactor: improve headers handling * refactor(frontend): use `fetch` instead of `axios` * refactor: remove axios package * refactor: add types and handlers --------- Co-authored-by: Marluan Espiritusanto <[email protected]>
- Loading branch information
1 parent
bc40fb5
commit 0861584
Showing
19 changed files
with
297 additions
and
314 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
namespace NodeJS { | ||
interface ProcessEnv { | ||
CEDULA_API?: string; | ||
CEDULA_API_KEY?: string; | ||
JCE_PHOTO_API?: string; | ||
JCE_PHOTO_API_KEY?: string; | ||
ENCRYPTION_KEY?: string; | ||
RECAPTHA_API_KEY?: string; | ||
RECAPTHA_PROJECT_ID?: string; | ||
SITE_COOKIE_KEY?: string; | ||
NEXT_PUBLIC_RECAPTCHA_SITE_KEY?: string; | ||
NEXT_PUBLIC_GTM_ID?: string; | ||
CEDULA_TOKEN_API?: string; | ||
CITIZENS_API_AUTH_KEY?: string; | ||
NEXT_PUBLIC_ORY_SDK_URL?: string; | ||
ORY_SDK_TOKEN?: string; | ||
PWNED_KEY?: string; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import type { CompareFacesCommandInput } from '@aws-sdk/client-rekognition'; | ||
import { NextRequest, NextResponse } from 'next/server'; | ||
|
||
import { getRekognitionClient } from '@/helpers'; | ||
import logger from '@/lib/logger'; | ||
import { | ||
LIVENESS_LOW_CONFIDENCE_ERROR, | ||
LIVENESS_NO_MATCH_ERROR, | ||
} from '@/constants'; | ||
|
||
type Props = { params: { sessionId: string; cedula: string } }; | ||
|
||
export async function GET( | ||
req: NextRequest, | ||
{ params: { sessionId, cedula } }: Props, | ||
) { | ||
const client = await getRekognitionClient(req); | ||
const response = await client.getFaceLivenessSessionResults({ | ||
SessionId: sessionId, | ||
}); | ||
|
||
const confidence = response.Confidence ?? 0; | ||
// Threshold for face liveness | ||
const isLive = confidence > 85; | ||
|
||
if (!isLive) { | ||
logger.warn(`Low confidence (${confidence}%) for citizen ${cedula}`); | ||
|
||
return NextResponse.json( | ||
{ | ||
message: LIVENESS_LOW_CONFIDENCE_ERROR, | ||
isLive, | ||
}, | ||
{ status: 403 }, | ||
); | ||
} | ||
|
||
logger.info(`High confidence (${confidence}%) for citizen ${cedula}`); | ||
|
||
if (response?.ReferenceImage?.Bytes) { | ||
const targetImageBuffer = await fetchPhotoBuffer(cedula); | ||
|
||
try { | ||
const params: CompareFacesCommandInput = { | ||
SourceImage: { | ||
Bytes: Buffer.from(response.ReferenceImage.Bytes), | ||
}, | ||
TargetImage: { | ||
Bytes: Buffer.from(targetImageBuffer), | ||
}, | ||
// Threshold for face match | ||
SimilarityThreshold: 95, | ||
}; | ||
|
||
const { FaceMatches } = await client.compareFaces(params); | ||
|
||
if (!FaceMatches?.length) { | ||
logger.warn(`Low similarity for citizen ${cedula}`); | ||
|
||
return NextResponse.json( | ||
{ | ||
message: LIVENESS_NO_MATCH_ERROR, | ||
isMatch: false, | ||
}, | ||
{ | ||
status: 404, | ||
}, | ||
); | ||
} | ||
|
||
const similarity = FaceMatches[0].Similarity; | ||
|
||
logger.info(`High similarity (${similarity}%) for citizen ${cedula}`); | ||
|
||
return NextResponse.json({ isMatch: true }); | ||
} catch (error) { | ||
logger.error(error); | ||
|
||
return NextResponse.json( | ||
{ | ||
message: LIVENESS_NO_MATCH_ERROR, | ||
isMatch: false, | ||
}, | ||
{ status: 500 }, | ||
); | ||
} | ||
} | ||
} | ||
|
||
const fetchPhotoBuffer = async (cedula: string) => { | ||
const photoUrl = new URL(`${process.env.JCE_PHOTO_API!}/${cedula}/photo`); | ||
photoUrl.searchParams.append('api-key', process.env.JCE_PHOTO_API_KEY!); | ||
|
||
return fetch(photoUrl).then((res) => res.arrayBuffer()); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,111 +1,14 @@ | ||
import { NextRequest, NextResponse } from 'next/server'; | ||
import axios from 'axios'; | ||
|
||
import { getRekognitionClient } from '@/helpers'; | ||
import logger from '@/lib/logger'; | ||
|
||
import { | ||
LIVENESS_LOW_CONFIDENCE_ERROR, | ||
LIVENESS_NO_MATCH_ERROR, | ||
} from '@/constants'; | ||
|
||
export async function GET( | ||
req: NextRequest, | ||
res: NextResponse<any | void>, | ||
): Promise<any> { | ||
const http = axios.create({ | ||
baseURL: process.env.JCE_PHOTO_API, | ||
}); | ||
const url = new URL(req.url); | ||
|
||
const sessionId = url.searchParams.get('sessionId'); | ||
const cedula = url.searchParams.get('cedula'); | ||
|
||
const SessionId = sessionId as string; | ||
|
||
const client = await getRekognitionClient(req); | ||
const response = await client.getFaceLivenessSessionResults({ | ||
SessionId, | ||
}); | ||
|
||
let isLive = false; | ||
const confidence = response.Confidence; | ||
|
||
// Threshold for face liveness | ||
if (confidence && confidence > 85) { | ||
logger.info(`High confidence (${confidence}%) for citizen ${cedula}`); | ||
isLive = true; | ||
} else { | ||
logger.warn(`Low confidence (${confidence}%) for citizen ${cedula}`); | ||
return NextResponse.json({ | ||
message: LIVENESS_LOW_CONFIDENCE_ERROR, | ||
isLive: isLive, | ||
status: 200, | ||
}); | ||
} | ||
|
||
if (isLive && response.ReferenceImage && response.ReferenceImage.Bytes) { | ||
const { data } = await http.get(`/${cedula}/photo`, { | ||
params: { | ||
'api-key': process.env.JCE_PHOTO_API_KEY, | ||
}, | ||
responseType: 'arraybuffer', | ||
}); | ||
|
||
const buffer1 = Buffer.from(response.ReferenceImage.Bytes); | ||
const buffer2 = Buffer.from(data, 'base64'); | ||
const params = { | ||
SourceImage: { | ||
Bytes: buffer1, | ||
}, | ||
TargetImage: { | ||
Bytes: buffer2, | ||
}, | ||
// Threshold for face match | ||
SimilarityThreshold: 95, | ||
}; | ||
|
||
try { | ||
const response = await client.compareFaces(params); | ||
if (response.FaceMatches && response.FaceMatches.length) { | ||
const similarity = response.FaceMatches[0].Similarity; | ||
logger.info(`High similarity (${similarity}%) for citizen ${cedula}`); | ||
return NextResponse.json({ | ||
isMatch: true, | ||
status: 200, | ||
}); | ||
} else { | ||
logger.warn(`Low similarity for citizen ${cedula}`); | ||
return NextResponse.json({ | ||
message: LIVENESS_NO_MATCH_ERROR, | ||
isMatch: false, | ||
status: 200, | ||
}); | ||
} | ||
} catch (error) { | ||
logger.error(error); | ||
return NextResponse.json({ | ||
message: LIVENESS_NO_MATCH_ERROR, | ||
isMatch: false, | ||
status: 500, | ||
}); | ||
} | ||
} | ||
} | ||
|
||
export async function POST( | ||
req: NextRequest, | ||
{ params }: { params: { sessionId: string } }, | ||
res: NextResponse<any | void>, | ||
): Promise<any> { | ||
export async function POST(req: NextRequest) { | ||
const client = await getRekognitionClient(req); | ||
|
||
const response = await client.createFaceLivenessSession({ | ||
const { SessionId: sessionId } = await client.createFaceLivenessSession({ | ||
// TODO: Create a unique token for each request, and reuse on retry | ||
// ClientRequestToken: req.cookies.token, | ||
}); | ||
return NextResponse.json({ | ||
sessionId: response.SessionId, | ||
status: 200, | ||
}); | ||
|
||
return NextResponse.json({ sessionId }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,23 @@ | ||
import { NextRequest, NextResponse } from 'next/server'; | ||
import axios from 'axios'; | ||
|
||
import { Identity } from '../../types'; | ||
import type { Identity } from '../../types'; | ||
import { unwrap } from '@/helpers'; | ||
|
||
export const dynamicParams = true; | ||
|
||
export async function GET( | ||
req: NextRequest, | ||
{ params }: { params: { cedula: string } }, | ||
): Promise<NextResponse> { | ||
const http = axios.create({ | ||
baseURL: process.env.NEXT_PUBLIC_ORY_SDK_URL, | ||
export async function GET(req: NextRequest, { params: { cedula } }: Props) { | ||
const url = new URL('admin/identities', process.env.NEXT_PUBLIC_ORY_SDK_URL); | ||
url.searchParams.append('credentials_identifier', cedula); | ||
|
||
const identity = await fetch(url, { | ||
headers: { | ||
Authorization: 'Bearer ' + process.env.ORY_SDK_TOKEN, | ||
Authorization: `Bearer ${process.env.ORY_SDK_TOKEN}`, | ||
}, | ||
}); | ||
|
||
const cedula = params.cedula; | ||
|
||
const { data: identity } = await http.get<Identity[]>( | ||
`/admin/identities?credentials_identifier=${cedula}`, | ||
); | ||
|
||
const exists = identity.length !== 0; | ||
}).then<Identity[]>(unwrap); | ||
|
||
return NextResponse.json({ | ||
exists: exists, | ||
status: 200, | ||
exists: identity.length !== 0, | ||
}); | ||
} | ||
|
||
type Props = { params: { cedula: string } }; |
Oops, something went wrong.