@@ -8,9 +8,9 @@ const defaultHeader = {
88 "api-version" : "1.0" ,
99} ;
1010
11- // Call Incode's `omni/start` API to create an Incode session which will include a
11+ // Public: Call Incode's `omni/start` API to create an Incode session which will include a
1212// token in the response.
13- const fakeBackendStart = async function ( ) {
13+ const start = async function ( identityId ) {
1414 const url = `${ apiurl } /omni/start` ;
1515 const params = {
1616 configurationId : flowid ,
@@ -34,102 +34,134 @@ const fakeBackendStart = async function () {
3434 const { token, interviewId } = responseData ;
3535
3636 // Store session in local DB, session will be created as used: false.
37- await addSession ( interviewId , token ) ;
37+ await addSession ( interviewId , token , identityId ) ;
3838
3939 return { token, interviewId } ;
4040} ;
4141
42- // Finishes the session started at /start
43- const fakeBackendFinish = async function ( token ) {
44- const url = `${ apiurl } /omni/finish-status` ;
45-
46- let sessionHeaders = { ...defaultHeader } ;
47- sessionHeaders [ "X-Incode-Hardware-Id" ] = token ;
48-
49- let response ;
50- try {
51- response = await fetch ( url , { method : "GET" , headers : sessionHeaders } ) ;
52- if ( ! response . ok ) {
53- throw new Error ( "Request failed with code " + response . status ) ;
54- }
55- } catch ( e ) {
56- throw new Error ( "HTTP Post Error: " + e . message ) ;
57- }
58- const { redirectionUrl, action } = await response . json ( ) ;
59- return { redirectionUrl, action } ;
60- } ;
61-
62- const fakeBackendValidateAuthentication = async function ( interviewId , token , candidateId ) {
42+ // Public: Verify the authentication by checking the score and session data
43+ const verifyAuthentication = async function ( interviewId , token , candidate ) {
6344 const session = await getSession ( interviewId ) ;
6445
46+ // Prevents usage of session that doesn't exist.
6547 if ( ! session ) {
6648 return {
49+ // Detailed debug message, in production you might want to avoid exposing internal details.
6750 message : "No session found for interviewId " + interviewId ,
6851 valid : false ,
6952 } ;
7053 }
54+
7155 // Prevents reuse of the same session.
72- if ( session . used ) {
56+ if ( session . status !== "pending" ) {
7357 return {
58+ // Detailed debug message, in production you might want to avoid exposing internal details.
7459 message : "Session already used for interviewId " + interviewId ,
7560 valid : false ,
7661 } ;
7762 }
7863
7964 // Prevents usage of token from another interviewId.
8065 if ( session . token !== token ) {
66+ // Mark the session as rejected.
67+ await updateSession ( interviewId , "rejected" ) ;
8168 return {
69+ // Detailed debug message, in production you might want to avoid exposing internal details.
8270 message : "Token mismatch for interviewId " + interviewId ,
8371 valid : false ,
8472 } ;
8573 }
74+
75+ // Prevents usage of candidate that doesn't match the identityId stored in session.
76+ if ( session . identityId !== candidate ) {
77+ // Mark the session as rejected.
78+ await updateSession ( interviewId , "rejected" ) ;
79+ return {
80+ // Detailed debug message, in production you might want to avoid exposing internal details.
81+ message : "Token identityId and candidate mismatch for interviewId " + interviewId ,
82+ valid : false ,
83+ } ;
84+ }
85+
86+ // Finishing the session stop it from being changed further and triggers score calculation and business rules.
87+ await finish ( token ) ; // Mark session as finished in Incode backend
8688
8789 let identityId , scoreStatus ;
8890 try {
8991 // At this point we already verified that the token matches, but
9092 // to be clear about our intentions, we use the token stored in the
91- // database to get the identityId and compare it with the candidateId .
92- const scoreResponse = await fakeBackendGetScore ( session . token ) ;
93+ // database to get the identityId and compare it with the candidate .
94+ const scoreResponse = await getScore ( session . token ) ;
9395 identityId = scoreResponse . authentication . identityId ;
9496 scoreStatus = scoreResponse . overall . status ;
9597 } catch ( e ) {
98+ // Mark the session as rejected.
99+ await updateSession ( interviewId , "rejected" ) ;
96100 // If there is an error communicating with API, we consider validation failed.
97101 return {
102+ // Detailed debug message, in production you might want to avoid exposing internal details.
98103 message : "Error validating authentication for interviewId " + interviewId + ": " + e . message ,
99104 valid : false ,
100105 } ;
101106 }
102107
103- // renderFaceAuth returns candidateId , which should match identityId from score,
108+ // renderFaceAuth returns candidate , which should match identityId from score,
104109 // this prevents tampering of the identityId in the frontend.
105- if ( identityId !== candidateId ) {
110+ if ( identityId !== candidate ) {
111+ // Mark the session as rejected.
112+ await updateSession ( interviewId , "rejected" ) ;
106113 return {
114+ // Detailed debug message, in production you might want to avoid exposing internal details.
107115 message : "Session data doesn't match for interviewId " + interviewId ,
108116 valid : false ,
109117 } ;
110118 }
111119
112120 // If backend score overall status is not OK, validation fails.
113121 if ( scoreStatus !== "OK" ) {
122+ // Mark the session as rejected.
123+ await updateSession ( interviewId , "rejected" ) ;
114124 return {
125+ // Detailed debug message, in production you might want to avoid exposing internal details.
115126 message : "Face Validation failed for interviewId " + interviewId ,
116127 valid : false ,
117128 } ;
118129 }
119130
120- // Mark session as used so it can't be used again
121- await markSessionAsUsed ( interviewId ) ;
131+ // Mark the session as approved since all checks passed.
132+ await updateSession ( interviewId , "approved" ) ;
122133
123134 // Only valid if all checks passed, we return the identityId that was validated.
124135 return {
136+ // Detailed debug message, in production you might want to avoid exposing internal details.
125137 message : "Face Validation succeeded for interviewId " + interviewId ,
126138 valid : true ,
127139 identityId : identityId ,
128140 } ;
129141} ;
130142
131- // Finishes the session started at /start
132- const fakeBackendGetScore = async function ( token ) {
143+ // Private: Calls Incode's `omni/finish-status` API mark the session as finished
144+ const finish = async function ( token ) {
145+ const url = `${ apiurl } /omni/finish-status` ;
146+
147+ let sessionHeaders = { ...defaultHeader } ;
148+ sessionHeaders [ "X-Incode-Hardware-Id" ] = token ;
149+
150+ let response ;
151+ try {
152+ response = await fetch ( url , { method : "GET" , headers : sessionHeaders } ) ;
153+ if ( ! response . ok ) {
154+ throw new Error ( "Request failed with code " + response . status ) ;
155+ }
156+ } catch ( e ) {
157+ throw new Error ( "HTTP Post Error: " + e . message ) ;
158+ }
159+ const { redirectionUrl, action } = await response . json ( ) ;
160+ return { redirectionUrl, action } ;
161+ } ;
162+
163+ // Private: Call Incode's `omni/get/score` API to retrieve the score for the session
164+ const getScore = async function ( token ) {
133165 const url = `${ apiurl } /omni/get/score` ;
134166
135167 let sessionHeaders = { ...defaultHeader } ;
@@ -234,15 +266,16 @@ async function getSession(interviewId) {
234266}
235267
236268// Add a new session to the database
237- async function addSession ( interviewId , token ) {
269+ async function addSession ( interviewId , token , identityId ) {
238270 const db = await initDB ( ) ;
239271 return new Promise ( ( resolve , reject ) => {
240272 const transaction = db . transaction ( [ STORE_NAME ] , "readwrite" ) ;
241273 const objectStore = transaction . objectStore ( STORE_NAME ) ;
242274 const session = {
243275 interviewId,
244276 token,
245- used : false ,
277+ identityId,
278+ status : "pending" ,
246279 timestamp : new Date ( ) . toISOString ( ) ,
247280 } ;
248281 const request = objectStore . add ( session ) ;
@@ -253,7 +286,12 @@ async function addSession(interviewId, token) {
253286}
254287
255288// Update validation status for a session
256- async function markSessionAsUsed ( interviewId ) {
289+ async function updateSession ( interviewId , status ) {
290+
291+ if ( status !== "rejected" && status !== "approved" ) {
292+ throw new Error ( "Invalid status. Must be 'rejected' or 'approved'." ) ;
293+ }
294+
257295 const db = await initDB ( ) ;
258296 return new Promise ( ( resolve , reject ) => {
259297 const transaction = db . transaction ( [ STORE_NAME ] , "readwrite" ) ;
@@ -263,7 +301,7 @@ async function markSessionAsUsed(interviewId) {
263301 getRequest . onsuccess = ( ) => {
264302 const session = getRequest . result ;
265303 if ( session ) {
266- session . used = true ;
304+ session . status = status ;
267305 const updateRequest = objectStore . put ( session ) ;
268306 updateRequest . onsuccess = ( ) => resolve ( session ) ;
269307 updateRequest . onerror = ( ) => reject ( updateRequest . error ) ;
@@ -275,4 +313,5 @@ async function markSessionAsUsed(interviewId) {
275313 } ) ;
276314}
277315
278- export { fakeBackendStart , fakeBackendFinish , fakeBackendGetScore , fakeBackendValidateAuthentication } ;
316+ const exampleBackend = { start, verifyAuthentication }
317+ export default exampleBackend ;
0 commit comments