* Ported from NodeJS to Python
* Migrated to libfprint-2
This commit is contained in:
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# YOUreka IDID Fingerprint Service
|
||||||
|
|
||||||
|
This service uses the libfprint library (version 2 and newwer) to capture fingerprints for profiles in YOUreka ID Domains.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
## Debian Packages:
|
||||||
|
1. sudo apt install libfprint-2-dev
|
||||||
|
2. sudo apt install python3-pip
|
||||||
|
|
||||||
|
## PIP Packages
|
||||||
|
1. pip3 install python-socketio
|
||||||
|
2. pip3 install eventlet
|
||||||
|
|
||||||
|
# How to run
|
||||||
|
```python3 app.py```
|
||||||
Binary file not shown.
@@ -0,0 +1,149 @@
|
|||||||
|
import eventlet
|
||||||
|
import socketio
|
||||||
|
import uuid
|
||||||
|
import base64
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version("FPrint", "2.0")
|
||||||
|
gi.require_version("Gio", "2.0")
|
||||||
|
from gi.repository import FPrint, GLib, Gio
|
||||||
|
|
||||||
|
|
||||||
|
sio = socketio.Server()
|
||||||
|
static_files = {
|
||||||
|
'/assets': './public',
|
||||||
|
'/': {'filename': './public/index.html'}
|
||||||
|
}
|
||||||
|
app = socketio.WSGIApp(sio, static_files=static_files)
|
||||||
|
|
||||||
|
stopEnrollment = False
|
||||||
|
def progress(device, completed_stages, prnt, sessionId, error):
|
||||||
|
print({'completedStages': completed_stages, 'sessionId': sessionId})
|
||||||
|
global stopEnrollment
|
||||||
|
if (error):
|
||||||
|
stopEnrollment = True
|
||||||
|
print({'error': error})
|
||||||
|
else:
|
||||||
|
if (completed_stages == 5):
|
||||||
|
if (not stopEnrollment):
|
||||||
|
sio.emit("fprint-enroll-status", { 'stage': completed_stages }, room=sessionId)
|
||||||
|
else:
|
||||||
|
print('Completed with an error.')
|
||||||
|
|
||||||
|
def enroll(sessionId, data):
|
||||||
|
ctx = FPrint.Context()
|
||||||
|
devices = ctx.get_devices()
|
||||||
|
if (len(devices) == 0):
|
||||||
|
print("There is no attached device.")
|
||||||
|
else:
|
||||||
|
device = devices[0]
|
||||||
|
if (not device):
|
||||||
|
print("There is no attached device")
|
||||||
|
|
||||||
|
if (not device.is_open()):
|
||||||
|
device.open_sync()
|
||||||
|
while (not device.is_open()):
|
||||||
|
print('Opening...')
|
||||||
|
try:
|
||||||
|
prt = FPrint.Print.new(device)
|
||||||
|
prt = device.enroll_sync(prt, None, progress_cb=progress, progress_data=sessionId)
|
||||||
|
img = prt.serialize()
|
||||||
|
sio.emit('fprint-enroll-done', { 'ok': True, "fingerprint": base64.b64encode(img).decode('utf-8'), 'id': str(uuid.uuid4()), 'data': data })
|
||||||
|
with open('adebisi-fa.jpg', 'w+b') as f:
|
||||||
|
f.write(img)
|
||||||
|
except Exception as e:
|
||||||
|
print({'error': e});
|
||||||
|
sio.emit('fprint-enroll-done', { 'ok': False, 'message': e})
|
||||||
|
finally:
|
||||||
|
device.close_sync()
|
||||||
|
|
||||||
|
def open_dev(sessionId):
|
||||||
|
device = None;
|
||||||
|
ctx = FPrint.Context()
|
||||||
|
devices = ctx.get_devices()
|
||||||
|
if (len(devices) == 0):
|
||||||
|
print("There is no attached device.")
|
||||||
|
else:
|
||||||
|
device = devices[0]
|
||||||
|
if (not device):
|
||||||
|
print("There is no attached device")
|
||||||
|
|
||||||
|
if (not device.is_open()):
|
||||||
|
device.open_sync()
|
||||||
|
while (not device.is_open()):
|
||||||
|
print('Opening...')
|
||||||
|
return device
|
||||||
|
|
||||||
|
def verify(sessionId, data):
|
||||||
|
ctx = FPrint.Context()
|
||||||
|
devices = ctx.get_devices()
|
||||||
|
if (len(devices) == 0):
|
||||||
|
print("There is no attached device.")
|
||||||
|
else:
|
||||||
|
device = devices[0]
|
||||||
|
if (not device):
|
||||||
|
print("There is no attached device")
|
||||||
|
|
||||||
|
if (not device.is_open()):
|
||||||
|
device.open_sync()
|
||||||
|
while (not device.is_open()):
|
||||||
|
print('Opening...')
|
||||||
|
|
||||||
|
with open('adebisi-fa.jpg', 'r+b') as f:
|
||||||
|
img = f.read()
|
||||||
|
prtRecord = FPrint.Print.deserialize(img)
|
||||||
|
canceller = Gio.Cancellable.new()
|
||||||
|
(match, prtFound) = device.verify_sync(prtRecord, canceller, None)
|
||||||
|
if (match):
|
||||||
|
sio.emit('fprint-verify-done', { 'ok': True, 'data': data }, room=sessionId)
|
||||||
|
else:
|
||||||
|
sio.emit('fprint-verify-done', { 'ok': False }, room=sessionId)
|
||||||
|
device.close_sync()
|
||||||
|
|
||||||
|
def verify_short(sessionId, data):
|
||||||
|
d = open_dev(sessionId)
|
||||||
|
with open('adebisi-fa.jpg', 'r+b') as f:
|
||||||
|
img = f.read()
|
||||||
|
prtRecord = FPrint.Print.deserialize(img)
|
||||||
|
(match, print) = d.verify_sync(prtRecord, None, None)
|
||||||
|
if (match):
|
||||||
|
sio.emit('fprint-verify-done', { 'ok': True, 'data': data }, room=sessionId)
|
||||||
|
else:
|
||||||
|
sio.emit('fprint-verify-done', { 'ok': False }, room=sessionId)
|
||||||
|
d.close_sync()
|
||||||
|
|
||||||
|
# events
|
||||||
|
|
||||||
|
@sio.on("fprint-verify")
|
||||||
|
def fprint_verify(sid, data):
|
||||||
|
verify_short(sid, data)
|
||||||
|
pass
|
||||||
|
|
||||||
|
@sio.on("fprint-enroll")
|
||||||
|
def fprint_enroll(sid, data):
|
||||||
|
print('Enrolling {}'.format(data['finger']))
|
||||||
|
enroll(sid, data)
|
||||||
|
pass
|
||||||
|
|
||||||
|
@sio.event
|
||||||
|
def capture(sid, data):
|
||||||
|
print(data)
|
||||||
|
pass
|
||||||
|
|
||||||
|
@sio.event
|
||||||
|
def connect(sid, data):
|
||||||
|
# print({ 'sid': sid, 'event': 'connect', 'data': data});
|
||||||
|
pass
|
||||||
|
|
||||||
|
@sio.event
|
||||||
|
def disconnect(sid):
|
||||||
|
print({ 'sid': sid, 'event': 'disconnect'});
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on('capture-other')
|
||||||
|
def capture_other(sid, data):
|
||||||
|
print({ "event": "capture_other", "data": data })
|
||||||
|
pass
|
||||||
|
|
||||||
|
eventlet.wsgi.server(eventlet.listen(('127.0.0.1', 9000), reuse_port=False), app)
|
||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
step: 0,
|
||||||
|
phone: '',
|
||||||
|
errorMessage: '',
|
||||||
|
otp: '',
|
||||||
|
busy: {
|
||||||
|
login: false
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
phone: '',
|
||||||
|
username: '',
|
||||||
|
roles: []
|
||||||
|
},
|
||||||
|
staff: {
|
||||||
|
reference: ''
|
||||||
|
},
|
||||||
|
fingerDetails: {
|
||||||
|
'right-thumb': { },
|
||||||
|
'right-index-finger': { }
|
||||||
|
},
|
||||||
|
socket: null
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
var userStr = localStorage.getItem("user");
|
||||||
|
if (userStr) {
|
||||||
|
this.user = JSON.parse(userStr);
|
||||||
|
this.step = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket = io();
|
||||||
|
|
||||||
|
this.socket.on("connect", () => { })
|
||||||
|
|
||||||
|
this.socket.on("fprint-enroll-done", (data) => {
|
||||||
|
console.log({ event: 'enroll-done', data});
|
||||||
|
if (data.ok) {
|
||||||
|
let { id, fingerprint, data: { finger }} = data;
|
||||||
|
this.step = 1.2;
|
||||||
|
this.fingerDetails[finger] = { id, fingerprint, finger }
|
||||||
|
}
|
||||||
|
this.busy.login = false;
|
||||||
|
})
|
||||||
|
|
||||||
|
this.socket.on("fprint-verify-done", (data) => {
|
||||||
|
console.log({ event: 'verify-done', data});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
authenticateUrl: function (api, authenticate = true) {
|
||||||
|
return `https://apis.a2loft.net/identity${api}` + (authenticate ? `?phone=${this.user.phone}&otp=${this.otp}` : '');
|
||||||
|
},
|
||||||
|
login: async function () {
|
||||||
|
this.busy.login = true;
|
||||||
|
var { data } = await axios.get(
|
||||||
|
this.authenticateUrl(`/validate/uc/${this.phone}/${this.otp}`, false)
|
||||||
|
);
|
||||||
|
this.errorMessage = '';
|
||||||
|
if (!data.ok)
|
||||||
|
this.errorMessage = "Invalid OTP specified.";
|
||||||
|
else {
|
||||||
|
localStorage.setItem("user", JSON.stringify(data.data));
|
||||||
|
this.user = data.data;
|
||||||
|
this.step = 1.0;
|
||||||
|
}
|
||||||
|
this.busy.login = false;
|
||||||
|
},
|
||||||
|
logout: function () {
|
||||||
|
localStorage.removeItem("user");
|
||||||
|
this.step = 0;
|
||||||
|
},
|
||||||
|
loadUserByReference: async function () {
|
||||||
|
this.busy.login = true;
|
||||||
|
try {
|
||||||
|
var { data } = await axios.get(
|
||||||
|
this.authenticateUrl(`/uc/${this.staff.reference}`)
|
||||||
|
);
|
||||||
|
if (!data.ok) {
|
||||||
|
swal("Oops!", `Operation failed. ${data.message}.` + this.staff.reference, "error");
|
||||||
|
} else {
|
||||||
|
this.staff = data.data;
|
||||||
|
// this.staff.verified = false;
|
||||||
|
this.step = 1.1;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.busy.login = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
captureFingerPrint: async function (finger) {
|
||||||
|
this.busy.login = true;
|
||||||
|
this.step = 1.1;
|
||||||
|
// var { data: { success, message: { id, data, fingerprint } } } = await axios.post("http://localhost:9000/api/user", {
|
||||||
|
// name: this.staff.reference
|
||||||
|
// });
|
||||||
|
// this.fingerDetails = { id, fingerprint };
|
||||||
|
// var { data } = await axios.delete(`http://localhost:9000/api/user/${id}`);
|
||||||
|
// console.log(`${id} purged.`);
|
||||||
|
this.socket.emit("fprint-enroll", { finger });
|
||||||
|
},
|
||||||
|
saveFingerPrint: async function () {
|
||||||
|
this.busy.login = true;
|
||||||
|
try {
|
||||||
|
var fingerprints = Object.keys(this.fingerprints).map(finger => {
|
||||||
|
return {
|
||||||
|
finger,
|
||||||
|
fingerprint: this.fingerprints[finger]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var { data } = await axios.post(
|
||||||
|
this.authenticateUrl(`/uc/fingerprints/${this.staff.reference}`),
|
||||||
|
{ fingerprints }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data.ok) {
|
||||||
|
swal("Saved", `Fingerprint saved for ${this.staff.name} with reference ${this.staff.reference}.`, "success");
|
||||||
|
this.newEnrolment();
|
||||||
|
} else {
|
||||||
|
swal("Oops!", `Operation failed. ${data.message}.` + this.staff.reference, "error");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.busy.login = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
newEnrolment: function () {
|
||||||
|
this.step = 1.0;
|
||||||
|
this.staff.reference = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Fingerprint Service</title>
|
||||||
|
<script src="https://cdn.socket.io/4.4.0/socket.io.min.js" integrity="sha384-1fOn6VtTq3PWwfsOrk45LnYcGosJwzMHv+Xh/Jx5303FVOXzEnw0EpLv30mtjmlj" crossorigin="anonymous"></script>
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||||
|
<style>
|
||||||
|
.disabledbutton {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="font-size: 14px; font-family: 'Segoe UI';">
|
||||||
|
<div class="container" id="app">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div style="margin-top: 100px;">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<h1>
|
||||||
|
UNICAL Biometrics Enrolment Wizard
|
||||||
|
</h1>
|
||||||
|
<hr />
|
||||||
|
<div v-if="step > 0" style="text-align: right">
|
||||||
|
<span style="cursor: pointer;" v-on:click="newEnrolment">+ New
|
||||||
|
Enrolment</span> •
|
||||||
|
<span>~{{user.username.substring(7)}}</span> • <span v-on:click='logout'
|
||||||
|
style="cursor: pointer; font-weight: bold;">Logout</span>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
<div v-if="step == 0">
|
||||||
|
<div style="background-color: whitesmoke; margin-left: auto; margin-right: auto; padding: 10px;">
|
||||||
|
<form class="form" style="padding: 20px;">
|
||||||
|
<h3>Agent Login</h3>
|
||||||
|
<br />
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Phone Number</label>
|
||||||
|
<input type="text" v-model="phone" class="form-control" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>One Time Password</label>
|
||||||
|
<input type="password" v-model="otp" class="form-control" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="btn btn-success btn-block" v-bind:class="{ disabledbutton: busy.login || !phone || !otp }"
|
||||||
|
v-on:click="login">
|
||||||
|
<span v-if="!busy.login">Login</span>
|
||||||
|
<span v-else>Please wait...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" v-if="errorMessage">
|
||||||
|
<div class="alert alert-danger">{{errorMessage}}</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="step >= 1">
|
||||||
|
<div v-if="step == 1.0">
|
||||||
|
<form class="form" style="padding: 20px;">
|
||||||
|
<h3>New Profile Enrollment</h3>
|
||||||
|
<br />
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Reference Number</label>
|
||||||
|
<input type="text" v-model="staff.reference" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>OTP</label>
|
||||||
|
<input type="text" v-model="otp" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="btn btn-success" v-bind:class="{ disabledbutton: busy.login || !staff.reference || !otp }"
|
||||||
|
v-on:click="loadUserByReference">
|
||||||
|
<span v-if="!busy.login">Get Details</span>
|
||||||
|
<span v-else>Please wait...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div v-if="step >= 1.1">
|
||||||
|
<form class="form form-horizontal" style="padding: 20px;">
|
||||||
|
<h3>Enroll Profile</h3>
|
||||||
|
<br />
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Reference Number</label>
|
||||||
|
<input type="text" readonly v-model="staff.reference" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Name</label>
|
||||||
|
<input type="text" readonly v-model="staff.name" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Reference</label>
|
||||||
|
<input type="text" readonly v-model="staff.domainReference" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Rank</label>
|
||||||
|
<input type="text" readonly v-model="staff.domainRankDescription" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Total Fingerprint Enrollment(s)</label>
|
||||||
|
<input type="text" readonly v-model="staff.fingerPrintCaptureCount" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group" v-if="step >= 1.1">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Finger</label>
|
||||||
|
<input type="text" readonly value="Right Thumb" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group" v-if="fingerDetails['right-thumb'].id">
|
||||||
|
<label>Enrolment ID</label>
|
||||||
|
<input type="text" readonly v-model="fingerDetails['right-thumb'].id" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="btn btn-success" style="display: inline" v-if="!fingerDetails['right-thumb'].id" v-bind:class="{ disabledbutton: busy.login }"
|
||||||
|
v-on:click="() => { captureFingerPrint('right-thumb') }">
|
||||||
|
<span v-if="!busy.login">Capture Fingerprint</span>
|
||||||
|
<span v-else>Please wait...</span>
|
||||||
|
</div>
|
||||||
|
<div class="btn btn-warning" style="display: inline" v-if="fingerDetails['right-thumb'].id" v-bind:class="{ disabledbutton: busy.login }"
|
||||||
|
v-on:click="() => { captureFingerPrint('right-thumb') }">
|
||||||
|
<span v-if="!busy.login">Recapture Fingerprint</span>
|
||||||
|
<span v-else>Please wait...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Finger</label>
|
||||||
|
<input type="text" readonly value="Right Index Finger" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group" v-if="fingerDetails['right-index-finger'].id">
|
||||||
|
<label>Enrolment ID</label>
|
||||||
|
<input type="text" readonly v-model="fingerDetails['right-index-finger'].id" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="btn btn-success" style="display: inline" v-if="!fingerDetails['right-index-finger'].id" v-bind:class="{ disabledbutton: busy.login }"
|
||||||
|
v-on:click="() => { captureFingerPrint('right-index-finger') }">
|
||||||
|
<span v-if="!busy.login">Capture Fingerprint</span>
|
||||||
|
<span v-else>Please wait...</span>
|
||||||
|
</div>
|
||||||
|
<div class="btn btn-warning" style="display: inline" v-if="fingerDetails['right-index-finger'].id" v-bind:class="{ disabledbutton: busy.login }"
|
||||||
|
v-on:click="() => { captureFingerPrint('right-index-finger') }">
|
||||||
|
<span v-if="!busy.login">Recapture Fingerprint</span>
|
||||||
|
<span v-else>Please wait...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Your profile has <span style="color: red; font-weight: bold;" v-if="!staff.verified">NOT </span>been verified. -->
|
||||||
|
</div>
|
||||||
|
<div v-if="fingerDetails['right-thumb'].id && fingerDetails['right-index-finger'].id">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>OTP</label>
|
||||||
|
<input type="text" v-model="otp" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="btn btn-success" v-bind:class="{ disabledbutton: busy.login || !otp }"
|
||||||
|
v-on:click="saveFingerPrint">
|
||||||
|
<span v-if="!busy.login">Save Finger Print</span>
|
||||||
|
<span v-else>Please wait...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- production version, optimized for size and speed -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
|
||||||
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
|
||||||
|
<script src="/assets/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
python-socketio
|
||||||
|
eventlet
|
||||||
Reference in New Issue
Block a user