Spaces:
Runtime error
Runtime error
Linh Vuu
commited on
Commit
·
c44d66d
1
Parent(s):
e1447d1
added files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .DS_Store +0 -0
- .gitignore +3 -0
- Flow/test_flow.html +12 -0
- Flow/testing tool_flow.drawio +1 -0
- Flow/testing tool_flow.jpeg +0 -0
- Flow/user_flow.drawio +1 -0
- Flow/user_flow.html +12 -0
- Flow/user_flow.jpeg +0 -0
- Mockup/.DS_Store +0 -0
- Mockup/Login.png +0 -0
- Mockup/Testing tool/1. Select Use Case.png +0 -0
- Mockup/Testing tool/2. Use Case Selected.png +0 -0
- Mockup/Testing tool/3. Upload Photo.png +0 -0
- Mockup/Testing tool/4. Passed.png +0 -0
- Mockup/Testing tool/5. Failed.png +0 -0
- Mockup/User/.DS_Store +0 -0
- Mockup/User/1. TransferMoney.png +0 -0
- Mockup/User/2. OTP.png +0 -0
- Mockup/User/3. UploadPhoto.png +0 -0
- Mockup/User/4. Passed.png +0 -0
- Mockup/User/5. Failed.png +0 -0
- README.md +2 -2
- app.py +158 -0
- baam_functions.py +766 -0
- backup-data.db +0 -0
- data.db +0 -0
- demo_user_flow.jpg +0 -0
- demo_user_profile.jpg +0 -0
- dummy_db.csv +0 -0
- face_verification/.DS_Store +0 -0
- face_verification/image/.DS_Store +0 -0
- face_verification/image/charlie-puth.jpg +0 -0
- face_verification/image/iu.jpeg +0 -0
- face_verification/image/lilitran2.JPG +0 -0
- face_verification/image/linhvuu.jpg +0 -0
- face_verification/image/linhvuu2.png +0 -0
- face_verification/image/linhvuu3.JPG +0 -0
- face_verification/test_deepface.ipynb +0 -0
- face_verification_helper.py +25 -0
- img/.DS_Store +0 -0
- img/Standard_Chartered.png +0 -0
- img/TestFail.png +0 -0
- img/TestPass.png +0 -0
- img/bank_sidebar.png +0 -0
- img/failed.png +0 -0
- img/failed_da.png +0 -0
- img/home.png +0 -0
- img/logo.png +0 -0
- img/menu.png +0 -0
- img/otp.jpg +0 -0
.DS_Store
ADDED
Binary file (10.2 kB). View file
|
|
.gitignore
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
.streamlit
|
2 |
+
__pycache__
|
3 |
+
myenv
|
Flow/test_flow.html
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->
|
2 |
+
<!DOCTYPE html>
|
3 |
+
<html>
|
4 |
+
<head>
|
5 |
+
<title>test_flow.html</title>
|
6 |
+
<meta charset="utf-8"/>
|
7 |
+
</head>
|
8 |
+
<body>
|
9 |
+
<div class="mxgraph" style="max-width:100%;border:1px solid transparent;" data-mxgraph="{"highlight":"#0000ff","nav":true,"resize":true,"xml":"<mxfile host=\"app.diagrams.net\" modified=\"2022-09-30T10:28:53.723Z\" agent=\"5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36\" etag=\"iMfvbQ_fRjlVJwybjWiR\" version=\"20.3.6\" type=\"device\"><diagram id=\"EapV6e3vjfq9qviyezT-\" name=\"Page-1\">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</diagram></mxfile>","toolbar":"pages zoom layers lightbox","page":0}"></div>
|
10 |
+
<script type="text/javascript" src="https://app.diagrams.net/js/viewer-static.min.js"></script>
|
11 |
+
</body>
|
12 |
+
</html>
|
Flow/testing tool_flow.drawio
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
<mxfile host="app.diagrams.net" modified="2022-09-25T07:34:59.322Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" etag="UO32HiYLzA1FkrK7jH-4" version="20.3.6" type="device"><diagram id="EapV6e3vjfq9qviyezT-" name="Page-1">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</diagram></mxfile>
|
Flow/testing tool_flow.jpeg
ADDED
![]() |
Flow/user_flow.drawio
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
<mxfile host="app.diagrams.net" modified="2022-09-25T07:37:24.734Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" etag="4rpihqY8uK4PeHr6fhQx" version="20.3.6" type="device"><diagram id="EapV6e3vjfq9qviyezT-" name="Page-1">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</diagram></mxfile>
|
Flow/user_flow.html
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->
|
2 |
+
<!DOCTYPE html>
|
3 |
+
<html>
|
4 |
+
<head>
|
5 |
+
<title>user_flow.html</title>
|
6 |
+
<meta charset="utf-8"/>
|
7 |
+
</head>
|
8 |
+
<body>
|
9 |
+
<div class="mxgraph" style="max-width:100%;border:1px solid transparent;" data-mxgraph="{"highlight":"#0000ff","nav":true,"resize":true,"xml":"<mxfile host=\"app.diagrams.net\" modified=\"2022-09-30T10:29:58.007Z\" agent=\"5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36\" etag=\"7vlyL5OUrrYoEm0dAe5a\" version=\"20.3.6\" type=\"device\"><diagram id=\"EapV6e3vjfq9qviyezT-\" name=\"Page-1\">7R1bd6M2+tf4nPbBOYAQ4Mcknunu2Wknp0m7nX3pkW3ZpoPBBTyJ++srgcRFkm2CuSjpPgVduPi7X5UJuN+9/BCj/fbHaIWDiWWsXiZgPrEs05055A+dOeYzNrTyiU3sr9imcuLR/wuzSYPNHvwVTmob0ygKUn9fn1xGYYiXaW0OxXH0XN+2joL6W/dog6WJxyUK5Nn/+qt0m8960Cjn/4X9zZa/2TTYyg7xzWwi2aJV9FyZAh8m4D6OojS/2r3c44ACj8Mlv+/jidXiw2Icpo1ucLDvfTI//pGAu+XPj58d58GaWnb+mG8oOLBfPLGcgDzwbkEuNvTidr/nc+TpxTT7VemRgyqODuEK07eZZPl566f4cY+WdPWZEAeZ26a7gC2jRRIFhxTfxkuG9Gy2HNlkmKRx9LWAO4HY3Tccpz7Bzm3gb0Iyl0b74lOqoGDQodvxS2WKgeYHHO1wGh/JFr7K0cToFLgwHz+XWLdmbM+2gvEZYBsRo7RN8ewSGeSC4eM1uIGXccMY7r1jxzYvYweAQbFjupex80uC4/ePnKlpN+AdZ1jekZDzKdr4YYGhmKPi3+H+kJKdB4KqEO0wedseJcnzimxAOwqhcJHsr8ZZF2B261C2ucSqQLmQYlUoc8hfA+Tgfjr9Pfl1bt6H/7HMZzj/37cnNQsIUMIrok7ZMIrTbbSJQhR8KGfvSjgaZFTu+RRRAs2g9wdO0yOjbnRIozpsCUjj42/s/mzwhQ5uIB/OX6qL8yMbncRJEh3iJT5DXcymSVG8wekZADFwUBicxXCMA5T63+omR+c84UjY+hCmVEAZnx+etCNv1xudvLWg7vZU6slUeo4uNKFST4L6U4zCZJ0R6i4K8VE7UnWgdSNrvN6IVW2OyBpPgpNsE1TgQtyTPd23e9lQT+5mHUTPyy2K05sVStECJfgEbDsAKPDqAPVsmfddBTRNsy9wApVf9HZUG37x098q15W7yKi8iQ6uV4fQbihpOteH7NaHyA/TCjkZdXKClkAm+Yeyu0pKuY1jdKxs29MNyen3TD3xPV71cRf327DmsZOL/AtKsi1g0p6S4Uyi5I/ID5TU/AktiDdZdzaYt7DEma0guxE7f7XKiR0n/l9okT2P0hODHnk4vJvAuZrCzvKeKD+KKA97y6QaSFEKauPGAwa7ty1R8S3Rep3gdCJKli5QpHL035KlYSkM4rOSQhNbw5JN4vstXn6llBbjFYGDj4JkdHvDMkf0/NSAk600mWLD1S0NwFLhERCX2l/WAVOnV66xzFdorOEdP6upomtqU1cwCBUI5HPXKsSZYLCKhJH/cEkhSg+yhUiPIxpgJzRrZ5JS9skeCGlJtEcYKxXM28z2vY+CiCixeUj8CDK59oNAmGqu71R8X6fpLlgfCiaGLTsaqsga6IvzXaCFrlqhZFtI4EIOmK+TA9noAcc+AQ3F9XmkXWR67oL1bd5Cx60zIXRvhDhqc362Lj2qZ452ZUdLydH6mKcFB3RhnpoeVxU8dn6dwO/UWFXHNN+6scoT0Jejt3oZq/y7zwE+3ka7xSEZxCqFDqzLDiCrJpVNY3eQ81Hiy2kAIL0ps2kcxTHHokw14PWwCTp3By5rfGMYjW8JAWfoNtP3nZndDaKhQ4oeyy7MFA4RU3aJuaCp2cV9CR89WKC97HEUNP8WcpqyQ1hJF2XFXRKhjhy9cUaP3nBp8maJlUP0crxRL2o1VYAXK1J4BDKINv5ydOqFQAxAjJ6W14N61RGIirXxpWZsdBGBqPLFOdl8UYaD2Vhsce6zKwj9/PSgq/gevy6lSWq6Vey9jLd/mZSR+Eux99dRfHuhryDucwUkmobe4ayj0HshiLsP1CmpTg773EdBQEvuLYNWiIzOqMDRTlPJTsET+orpq+iP3UZEp9B2hvxn+mv9KptMwxgbiFAPz6ptjqG9MjcV9XvKjU0F3jDK3FOVTeY2brJHYQ1xzp8H2v1yt8xzcLf0IzcL9B2hJfJyQ/jzfQZOYx2F6XSNdn5wzG/ZRWGU5FxRrCcZHumqsX/J52lOcMryBbfZZ6A4L8Y3Fmj5dZORyLT+Kd9Ztpe/3bJn7AKyD+EfXxb1Q7EPChK40dmsyacYcTjCDJJkZk6vKVNBCitIwH9pr1ns5aTV6jFW+ZgcXcVKjNNDHGYrMU4OQVruzNFX7CwTM2Qq5xi6ZmbDHNx0zBI0dLKeoqGLLElDF8s0DV0wsjleh5W/ElJIw3m2Uvn+XMapIFTNyijAJEGwuLUAXslEkIrKYqdtu+VjjpUnGACUCygpVzaVJ4lIyoYFpqqTdfph+wRCoxir9JrkvHai3WSUZNp56X51Ms24AcCp59Ks6yy4/nNpnuz5cMYr2O6fgTrL8eqGh+6oM+UKqI9UBTFTjoA+9aNwdINODN9oYNBZMtFLYGpZqZ6p9N9PQbYL+9gS4GkDGZxAkXcQnbXuoCmXMI5hHrc3cy1F0vMsx2li57pNSiD7B3y9rm2F1+iQmQDD1ER1h10bdI3d7NbXlvS7QrTPE5vqL+w3gQcF0rqupl9Ne3JN/0+RxqZCwStdlEzNgAl1tw4Uxyr460neZUy54VJHW9tOLbz0E2p29KYAoRCFdF2FOWEozIlisnsNKHchf8HvuCRYRIGnSEj0VRKszibJJoiyw+jdYsA1Vf2fg+LAkTXCGNZI6zxom+4PMujOUAFNo628qkgXM7RhR4Q+xkBBql0YA4bDi+HbGgNH5Q39mQaaFN9cy6mv6SweiVOhZg6jHPfQpxH3PKl2EOEzHVAPzmpvwwPZwc9s+LPxPW0NdwtYQsmsaSpqZl1F7Kq/QpYGkUCtY1e8juqNpWhtVexaa0lUUEoXNoPrgnoDHT/pR19ZZOt2HMmbM++bcmr35n2rOCSw6/mwmXk+DinuNw13gDikLXudmjsfBSN1IUgcAN9Y1hLICMtsGlZrLp54eOEERI2NnbqpowqRqc6T6q+ITY+z5IYvYmvqq5mdp3+uK2KT9W2M/zzghLKpv1PVpesj4+ApfmnVoA5mdo2XtG9Qd/RIzPbPM0Avr+J8QQxa5v6yLk0dgi+sLIsxh1QRdoM655YKlwienR+ilKZUeksMCqcBmECRGVT1WfSWEoGaBFpbNNe0lx3cfRmh3uI64aEHsmpNfYOU1rRxjbslj4HOMrpOOMrdR49ZzbYm6mQmBVctRXBVqVBgX/JPk5O6Wh9he45bhhGmsHFOWC9hCtXxXYJFTfiFGAca8otCxjzd/iyfjP9u6limli3KraJddaxCFksPqXVK97cXJvx3DH9I9nVcofivET/N3y9PQKGL2FQdtjsoR3hNOOJV/f4nDVulLWq1O4TX6rEzFjTUy5odBSCQ1kw8PLfxUQBikboYlOj5KABP1pRF25ruwVGvw641KTaqfyLZa9Iu9I8SJo0zFP8XJv1QpFy+77P/yaa9LOHM1M1JwIb2JXHe6479ZxZeLajWWo7UXX6joRip16mYvUkRlUly1T/bGkiKiJ0MYpdOUyki/YcB0U1vLUUm7PiDyvby4APw4W8=</diagram></mxfile>","toolbar":"pages zoom layers lightbox","page":0}"></div>
|
10 |
+
<script type="text/javascript" src="https://app.diagrams.net/js/viewer-static.min.js"></script>
|
11 |
+
</body>
|
12 |
+
</html>
|
Flow/user_flow.jpeg
ADDED
![]() |
Mockup/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
Mockup/Login.png
ADDED
![]() |
Mockup/Testing tool/1. Select Use Case.png
ADDED
![]() |
Mockup/Testing tool/2. Use Case Selected.png
ADDED
![]() |
Mockup/Testing tool/3. Upload Photo.png
ADDED
![]() |
Mockup/Testing tool/4. Passed.png
ADDED
![]() |
Mockup/Testing tool/5. Failed.png
ADDED
![]() |
Mockup/User/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
Mockup/User/1. TransferMoney.png
ADDED
![]() |
Mockup/User/2. OTP.png
ADDED
![]() |
Mockup/User/3. UploadPhoto.png
ADDED
![]() |
Mockup/User/4. Passed.png
ADDED
![]() |
Mockup/User/5. Failed.png
ADDED
![]() |
README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
---
|
2 |
title: Banking Advanced Authentication System
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
colorTo: yellow
|
6 |
sdk: streamlit
|
7 |
sdk_version: 1.31.0
|
|
|
1 |
---
|
2 |
title: Banking Advanced Authentication System
|
3 |
+
emoji: 🏦
|
4 |
+
colorFrom: pink
|
5 |
colorTo: yellow
|
6 |
sdk: streamlit
|
7 |
sdk_version: 1.31.0
|
app.py
ADDED
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Import libraries
|
2 |
+
import os
|
3 |
+
import streamlit as st
|
4 |
+
from PIL import Image
|
5 |
+
from streamlit_extras.switch_page_button import switch_page
|
6 |
+
from baam_functions import *
|
7 |
+
from datetime import datetime
|
8 |
+
from bokeh.models.widgets import Button
|
9 |
+
from bokeh.models import CustomJS
|
10 |
+
from streamlit_bokeh_events import streamlit_bokeh_events
|
11 |
+
|
12 |
+
# Set direction as current folder
|
13 |
+
sourceFileDir = os.path.dirname(os.path.abspath(__file__))
|
14 |
+
os.chdir(sourceFileDir)
|
15 |
+
|
16 |
+
logo = Image.open('img/logo.png')
|
17 |
+
st.set_page_config(page_title = "BAAM", page_icon = logo)
|
18 |
+
|
19 |
+
# Initialise variables to pass between pages
|
20 |
+
st.session_state['user_dict'] = {}
|
21 |
+
st.session_state['verification'] = False
|
22 |
+
st.session_state['location'] = ""
|
23 |
+
st.session_state['raw_location'] = ""
|
24 |
+
st.session_state['username'] = ""
|
25 |
+
# st.session_state['typing_speed'] = 0
|
26 |
+
|
27 |
+
def main():
|
28 |
+
"""Banking Advanced Authentication System"""
|
29 |
+
|
30 |
+
menu = ["Home", "SignUp", "Login"]
|
31 |
+
choice = st.sidebar.selectbox("Menu", menu)
|
32 |
+
|
33 |
+
if choice == "Home":
|
34 |
+
st.subheader("Banking Advanced Authentication Module")
|
35 |
+
st.image("img/home.png")
|
36 |
+
st.write("Please select one action from the Menu on the side bar. Thank you.")
|
37 |
+
st.image("img/menu.png")
|
38 |
+
|
39 |
+
elif choice == "SignUp":
|
40 |
+
st.subheader("Create New Account")
|
41 |
+
new_user = st.text_input("Username")
|
42 |
+
new_password = st.text_input("Password",type='password')
|
43 |
+
|
44 |
+
if st.button("Signup"):
|
45 |
+
create_user_table()
|
46 |
+
add_user_data(new_user,make_hashes(new_password))
|
47 |
+
st.success("You have successfully created a valid Account")
|
48 |
+
st.info("Go to Login Menu to login")
|
49 |
+
|
50 |
+
elif choice == "Login":
|
51 |
+
|
52 |
+
# Header of the page
|
53 |
+
col1, col2, col3 = st.columns(3)
|
54 |
+
with col1:
|
55 |
+
st.write(' ')
|
56 |
+
|
57 |
+
# Logo
|
58 |
+
with col2:
|
59 |
+
st.image("img/Standard_Chartered.png", width=175)
|
60 |
+
with col3:
|
61 |
+
st.write(' ')
|
62 |
+
|
63 |
+
# Login form
|
64 |
+
st.subheader("Login")
|
65 |
+
username = st.text_input("Username")
|
66 |
+
password = st.text_input("Password",type='password')
|
67 |
+
|
68 |
+
is_tester = st.checkbox('Login to test')
|
69 |
+
login_button = Button(label="Login")
|
70 |
+
|
71 |
+
# Collect location when login button is clicked
|
72 |
+
login_button.js_on_event("button_click", CustomJS(code="""
|
73 |
+
navigator.geolocation.getCurrentPosition(
|
74 |
+
(loc) => {
|
75 |
+
document.dispatchEvent(new CustomEvent("GET_LOCATION", {detail: {lat: loc.coords.latitude, lon: loc.coords.longitude}}))
|
76 |
+
}
|
77 |
+
)
|
78 |
+
"""))
|
79 |
+
|
80 |
+
# Get location
|
81 |
+
location = streamlit_bokeh_events(
|
82 |
+
login_button,
|
83 |
+
events="GET_LOCATION",
|
84 |
+
key="get_location",
|
85 |
+
refresh_on_update=False,
|
86 |
+
override_height=75,
|
87 |
+
debounce_time=0)
|
88 |
+
|
89 |
+
# If can get location
|
90 |
+
if location:
|
91 |
+
|
92 |
+
# Hash password & verify password
|
93 |
+
hashed_pswd = make_hashes(password)
|
94 |
+
result = login_user(username,check_hashes(password, hashed_pswd))
|
95 |
+
|
96 |
+
# When the username and password are correct
|
97 |
+
if result:
|
98 |
+
|
99 |
+
# time_start = st.session_state['time_start']
|
100 |
+
|
101 |
+
# Stop counting time after the user finished entering username, password and clicked login button
|
102 |
+
login_time = datetime.now()
|
103 |
+
|
104 |
+
# # *** Calculate the login duration in seconds (the time it takes user to enter username, password and click login button)
|
105 |
+
# login_duration = login_time - time_start
|
106 |
+
# login_duration_seconds = login_duration.total_seconds()
|
107 |
+
|
108 |
+
# # Calculate typing speed (characters per minute)
|
109 |
+
# typing_speed = (len(username) + len(password)) * 60 / login_duration_seconds
|
110 |
+
|
111 |
+
# Launch app for end user
|
112 |
+
if not(is_tester):
|
113 |
+
|
114 |
+
# Collect user's information when logging in
|
115 |
+
#user_dict, st.session_state['location'] = collect_data(username, location, login_time, typing_speed)
|
116 |
+
user_dict, st.session_state['location'] = collect_data(username, location, login_time)
|
117 |
+
|
118 |
+
# Store user information to pass to other pages
|
119 |
+
st.session_state['user_dict'] = user_dict
|
120 |
+
verification = verify_user(user_dict)
|
121 |
+
|
122 |
+
# THAO LE CODE:
|
123 |
+
# weight edit in logic_tl
|
124 |
+
weight = {'device_uuid': 40, 'mac_address': 40, 'device_name': 30.0, 'device_model': 20.0, \
|
125 |
+
'device_vendor': 4.0, 'ip_v4': 30, 'isp_name': 15.0, 'ip_country': 3.0, 'suburd': 30, 'district': 22.5, \
|
126 |
+
'city': 15.0, 'country': 3.0}
|
127 |
+
# ThaoLe note:
|
128 |
+
# st.write(f"print the user_dict {user_dict}")
|
129 |
+
# st.write(f'type of user_dict {type(user_dict)}')
|
130 |
+
# end not
|
131 |
+
|
132 |
+
st.session_state['verification'] = verification
|
133 |
+
|
134 |
+
# Add to the login database after successful verification
|
135 |
+
if verification:
|
136 |
+
add_login_data(user_dict)
|
137 |
+
|
138 |
+
# Open Transfer Money Page
|
139 |
+
switch_page("Transfer Money 💸")
|
140 |
+
|
141 |
+
# Launch Testing Tool
|
142 |
+
else:
|
143 |
+
st.session_state['raw_location'] = location
|
144 |
+
st.session_state['username'] = username
|
145 |
+
# st.session_state['typing_speed'] = typing_speed
|
146 |
+
switch_page("SelectUseCase")
|
147 |
+
|
148 |
+
else:
|
149 |
+
st.warning("Incorrect Username/Password")
|
150 |
+
|
151 |
+
# Start counting time
|
152 |
+
st.session_state['time_start'] = datetime.now()
|
153 |
+
|
154 |
+
# #To export database to csv file (can be commented out if not needed)
|
155 |
+
# export_csv()
|
156 |
+
|
157 |
+
if __name__ == '__main__':
|
158 |
+
main()
|
baam_functions.py
ADDED
@@ -0,0 +1,766 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Import libraries
|
2 |
+
import os
|
3 |
+
import streamlit as st
|
4 |
+
import pandas as pd
|
5 |
+
import platform, uuid, psutil
|
6 |
+
import requests
|
7 |
+
import json
|
8 |
+
from requests import get
|
9 |
+
from geopy.geocoders import Nominatim
|
10 |
+
from getmac import get_mac_address as gma
|
11 |
+
from pathlib import Path
|
12 |
+
from streamlit_extras.switch_page_button import switch_page
|
13 |
+
|
14 |
+
from geopy.distance import geodesic as GD
|
15 |
+
from datetime import datetime, timedelta
|
16 |
+
import matplotlib
|
17 |
+
# from face_verification_helper import face_verfication
|
18 |
+
|
19 |
+
# Security
|
20 |
+
#passlib,hashlib,bcrypt,scrypt
|
21 |
+
import hashlib
|
22 |
+
|
23 |
+
# DB Management
|
24 |
+
import sqlite3
|
25 |
+
|
26 |
+
def make_hashes(password):
|
27 |
+
return hashlib.sha256(str.encode(password)).hexdigest()
|
28 |
+
|
29 |
+
def check_hashes(password, hashed_text):
|
30 |
+
if make_hashes(password) == hashed_text:
|
31 |
+
return hashed_text
|
32 |
+
return False
|
33 |
+
|
34 |
+
# DB Functions
|
35 |
+
# Create table store username and password
|
36 |
+
def create_user_table():
|
37 |
+
|
38 |
+
# Access database
|
39 |
+
conn = sqlite3.connect('data.db')
|
40 |
+
c = conn.cursor()
|
41 |
+
c.execute('CREATE TABLE IF NOT EXISTS users(user_id INTEGER PRIMARY KEY AUTOINCREMENT,\
|
42 |
+
username TEXT NOT NULL, password TEXT NOT NULL)')
|
43 |
+
c.close()
|
44 |
+
|
45 |
+
def add_user_data(username, password):
|
46 |
+
|
47 |
+
# Access database
|
48 |
+
conn = sqlite3.connect('data.db')
|
49 |
+
c = conn.cursor()
|
50 |
+
c.execute('INSERT INTO users(username, password) VALUES (?,?)',(username,password))
|
51 |
+
conn.commit()
|
52 |
+
c.close()
|
53 |
+
|
54 |
+
def login_user(username, password):
|
55 |
+
|
56 |
+
# Access database
|
57 |
+
conn = sqlite3.connect('data.db')
|
58 |
+
c = conn.cursor()
|
59 |
+
c.execute('SELECT * FROM users WHERE username =? AND password = ?',(username,password))
|
60 |
+
data = c.fetchall()
|
61 |
+
c.close()
|
62 |
+
return data
|
63 |
+
|
64 |
+
# def view_all_users():
|
65 |
+
|
66 |
+
# Access database
|
67 |
+
# conn = sqlite3.connect('data.db')
|
68 |
+
# c = conn.cursor()
|
69 |
+
# c.execute('SELECT * FROM users')
|
70 |
+
# data = c.fetchall()
|
71 |
+
# c.close()
|
72 |
+
# return data
|
73 |
+
|
74 |
+
# Export data to CSV
|
75 |
+
def export_csv():
|
76 |
+
|
77 |
+
# Access database
|
78 |
+
conn = sqlite3.connect('data.db')
|
79 |
+
|
80 |
+
# Export table login
|
81 |
+
db_df = pd.read_sql_query('SELECT * FROM login', conn)
|
82 |
+
db_df.to_csv('login.csv', index=False)
|
83 |
+
|
84 |
+
# Export table users
|
85 |
+
db_df = pd.read_sql_query('SELECT * FROM users', conn)
|
86 |
+
db_df.to_csv('users.csv', index=False)
|
87 |
+
|
88 |
+
# Create table to store login data
|
89 |
+
def create_login_table():
|
90 |
+
# c.execute('DROP TABLE login')
|
91 |
+
|
92 |
+
# Access database
|
93 |
+
conn = sqlite3.connect('data.db')
|
94 |
+
c = conn.cursor()
|
95 |
+
|
96 |
+
c.execute('CREATE TABLE IF NOT EXISTS login(login_id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL,\
|
97 |
+
login_time TEXT NOT NULL,\
|
98 |
+
device_name TEXT, device_uuid TEXT, mac_address TEXT, device_vendor TEXT, device_model TEXT, device_ram TEXT,\
|
99 |
+
ip_v4 TEXT, ip_country TEXT, ip_region TEXT, ip_city TEXT, ip_lat TEXT, ip_lon TEXT, isp_name TEXT, isp_org TEXT,\
|
100 |
+
is_vpn TEXT, is_proxy TEXT, is_tor TEXT, is_relay TEXT,\
|
101 |
+
lat TEXT, lon TEXT, suburb TEXT, district TEXT, city TEXT, country TEXT)')
|
102 |
+
|
103 |
+
c.close()
|
104 |
+
|
105 |
+
# Add login data to database
|
106 |
+
def add_login_data(user_dict):
|
107 |
+
|
108 |
+
# Create login table if not existed
|
109 |
+
create_login_table()
|
110 |
+
|
111 |
+
# Get data from user dictionary
|
112 |
+
username, login_time,\
|
113 |
+
device_name, device_uuid, mac_address, device_vendor, device_model, device_ram,\
|
114 |
+
ip_v4, ip_country, ip_region, ip_city, ip_lat, ip_lon, isp_name, isp_org,\
|
115 |
+
is_vpn, is_proxy, is_tor, is_relay,\
|
116 |
+
lat, lon, suburb, district, city, country= get_from_user_dict(user_dict)
|
117 |
+
|
118 |
+
# Access database
|
119 |
+
conn = sqlite3.connect('data.db')
|
120 |
+
c = conn.cursor()
|
121 |
+
|
122 |
+
# Create table to store login information if not existed
|
123 |
+
c.execute('INSERT INTO login(username, login_time,\
|
124 |
+
device_name, device_uuid, mac_address, device_vendor, device_model, device_ram,\
|
125 |
+
ip_v4, ip_country, ip_region, ip_city, ip_lat, ip_lon, isp_name, isp_org,\
|
126 |
+
is_vpn, is_proxy, is_tor, is_relay,\
|
127 |
+
lat, lon, suburb, district, city, country)\
|
128 |
+
VALUES (?, ?,\
|
129 |
+
?, ?, ?, ?, ?, ?,\
|
130 |
+
?, ?, ?, ?, ?, ?, ?, ?,\
|
131 |
+
?, ?, ?, ?,\
|
132 |
+
?, ?, ?, ?, ?, ?)',\
|
133 |
+
(username, login_time,\
|
134 |
+
device_name, device_uuid, mac_address, device_vendor, device_model, device_ram,\
|
135 |
+
ip_v4, ip_country, ip_region, ip_city, ip_lat, ip_lon, isp_name, isp_org,\
|
136 |
+
is_vpn, is_proxy, is_tor, is_relay,\
|
137 |
+
lat, lon, suburb, district, city, country))
|
138 |
+
|
139 |
+
conn.commit()
|
140 |
+
c.close()
|
141 |
+
|
142 |
+
# Get login data of the user
|
143 |
+
def get_login_data(username):
|
144 |
+
|
145 |
+
# Create login table if not existed
|
146 |
+
create_login_table()
|
147 |
+
|
148 |
+
# Access database
|
149 |
+
conn = sqlite3.connect('data.db')
|
150 |
+
c = conn.cursor()
|
151 |
+
|
152 |
+
c.execute('SELECT * FROM login WHERE username = ?',(username,))
|
153 |
+
records = c.fetchall()
|
154 |
+
c.close()
|
155 |
+
|
156 |
+
return records
|
157 |
+
|
158 |
+
# Get data from user dictionary
|
159 |
+
def get_from_user_dict(user_dict):
|
160 |
+
|
161 |
+
username = user_dict.get('username', '')
|
162 |
+
login_time = user_dict.get('login_time', '')
|
163 |
+
# typing_speed = user_dict.get('typing_speed', '')
|
164 |
+
device_name = user_dict.get('device_name', '')
|
165 |
+
device_uuid = user_dict.get('device_uuid', '')
|
166 |
+
mac_address = user_dict.get('mac_address', '')
|
167 |
+
device_vendor = user_dict.get('device_vendor', '')
|
168 |
+
device_model = user_dict.get('device_model', '')
|
169 |
+
device_ram = user_dict.get('device_ram', '')
|
170 |
+
ip_v4 = user_dict.get('ip_v4', '')
|
171 |
+
ip_country = user_dict.get('ip_country', '')
|
172 |
+
ip_region = user_dict.get('ip_region', '')
|
173 |
+
ip_city = user_dict.get('ip_city', '')
|
174 |
+
ip_lat = user_dict.get('ip_lat', '')
|
175 |
+
ip_lon = user_dict.get('ip_lon', '')
|
176 |
+
isp_name = user_dict.get('isp_name', '')
|
177 |
+
isp_org = user_dict.get('isp_org', '')
|
178 |
+
is_vpn = user_dict.get('is_vpn', '')
|
179 |
+
is_proxy = user_dict.get('is_proxy', '')
|
180 |
+
is_tor = user_dict.get('is_tor', '')
|
181 |
+
is_relay = user_dict.get('is_relay', '')
|
182 |
+
lat = user_dict.get('lat', '')
|
183 |
+
lon = user_dict.get('lon', '')
|
184 |
+
suburb = user_dict.get('suburb', '')
|
185 |
+
district = user_dict.get('district', '')
|
186 |
+
city = user_dict.get('city', '')
|
187 |
+
country = user_dict.get('country', '')
|
188 |
+
|
189 |
+
|
190 |
+
return username, login_time,\
|
191 |
+
device_name, device_uuid, mac_address, device_vendor, device_model, device_ram,\
|
192 |
+
ip_v4, ip_country, ip_region, ip_city, ip_lat, ip_lon, isp_name, isp_org,\
|
193 |
+
is_vpn, is_proxy, is_tor, is_relay,\
|
194 |
+
lat, lon, suburb, district, city, country
|
195 |
+
|
196 |
+
def get_from_api(url, value=""):
|
197 |
+
|
198 |
+
# Use get method to fetch details from URL API
|
199 |
+
response = get(url + value)
|
200 |
+
if response.status_code != 200:
|
201 |
+
raise Exception("[!] Invalid request!")
|
202 |
+
|
203 |
+
return response.content.decode()
|
204 |
+
|
205 |
+
def get_ip_info(ip_v4):
|
206 |
+
|
207 |
+
# Get information from the ipv4
|
208 |
+
isp = get_from_api("http://ip-api.com/json/", ip_v4)
|
209 |
+
|
210 |
+
# Convert dictionary string to dictionary
|
211 |
+
isp = json.loads(isp)
|
212 |
+
|
213 |
+
# Get information from the dictionary
|
214 |
+
ip_country = isp["country"]
|
215 |
+
ip_region = isp["regionName"]
|
216 |
+
ip_city = isp["city"]
|
217 |
+
ip_lat = isp["lat"]
|
218 |
+
ip_lon = isp["lon"]
|
219 |
+
isp_name = isp["isp"]
|
220 |
+
isp_org = isp["org"]
|
221 |
+
|
222 |
+
# Detect VPN / proxy / tor
|
223 |
+
vpn_api_key = st.secrets["vpn_api_key"]
|
224 |
+
response = requests.get("https://vpnapi.io/api/" + ip_v4 + "?key=" + vpn_api_key)
|
225 |
+
data = json.loads(response.text)
|
226 |
+
|
227 |
+
is_vpn = data["security"]['vpn']
|
228 |
+
is_proxy = data["security"]['proxy']
|
229 |
+
is_tor = data["security"]['tor']
|
230 |
+
is_relay = data["security"]['relay']
|
231 |
+
|
232 |
+
return ip_country, ip_region, ip_city, ip_lat, ip_lon, isp_name, isp_org, is_vpn, is_proxy, is_tor, is_relay
|
233 |
+
|
234 |
+
def get_location(lat, lon):
|
235 |
+
|
236 |
+
suburb = ''
|
237 |
+
district = ''
|
238 |
+
city = ''
|
239 |
+
country = ''
|
240 |
+
|
241 |
+
# Get address from given coordinate
|
242 |
+
geolocator = Nominatim(user_agent="BAAM")
|
243 |
+
|
244 |
+
location = geolocator.reverse(lat + "," + lon)
|
245 |
+
address = location.raw['address']
|
246 |
+
|
247 |
+
suburb = address.get('suburb', '')
|
248 |
+
|
249 |
+
if address.get('city_district', ''):
|
250 |
+
district = address.get('city_district', '')
|
251 |
+
else:
|
252 |
+
district = address.get('district', '')
|
253 |
+
|
254 |
+
city = address.get('city', '')
|
255 |
+
country = address.get('country', '')
|
256 |
+
|
257 |
+
return location, suburb, district, city, country
|
258 |
+
|
259 |
+
# def collect_data(username, result, login_time, typing_speed):
|
260 |
+
def collect_data(username, result, login_time):
|
261 |
+
|
262 |
+
lat = ''
|
263 |
+
lon = ''
|
264 |
+
suburb = ''
|
265 |
+
district = ''
|
266 |
+
city = ''
|
267 |
+
country = ''
|
268 |
+
|
269 |
+
if "GET_LOCATION" in result:
|
270 |
+
lat = str(result.get("GET_LOCATION")["lat"])
|
271 |
+
lon = str(result.get("GET_LOCATION")["lon"])
|
272 |
+
|
273 |
+
if lat and lon:
|
274 |
+
location, suburb, district, city, country = get_location(lat, lon)
|
275 |
+
|
276 |
+
# Collect device information
|
277 |
+
device_name = platform.node()
|
278 |
+
device_uuid = uuid.getnode()
|
279 |
+
mac_address = gma()
|
280 |
+
device_vendor = get_from_api("https://api.macvendors.com/", mac_address)
|
281 |
+
device_model = platform.platform()
|
282 |
+
device_ram = str(round(psutil.virtual_memory().total / (1024.0 **3)))+" GB"
|
283 |
+
|
284 |
+
# Collect IP information
|
285 |
+
ip_v4 = get_from_api('https://api.ipify.org')
|
286 |
+
ip_country, ip_region, ip_city, ip_lat, ip_lon, isp_name, isp_org, is_vpn, is_proxy, is_tor, is_relay = get_ip_info(ip_v4)
|
287 |
+
|
288 |
+
user_dict = {
|
289 |
+
"username": username,
|
290 |
+
"login_time": login_time,
|
291 |
+
# "typing_speed": typing_speed,
|
292 |
+
"device_name": device_name,
|
293 |
+
"device_uuid": device_uuid,
|
294 |
+
"mac_address": mac_address,
|
295 |
+
"device_vendor": device_vendor,
|
296 |
+
"device_model": device_model,
|
297 |
+
"device_ram": device_ram,
|
298 |
+
"ip_v4": ip_v4,
|
299 |
+
"ip_country": ip_country,
|
300 |
+
"ip_region": ip_region,
|
301 |
+
"ip_city": ip_city,
|
302 |
+
"ip_lat": ip_lat,
|
303 |
+
"ip_lon": ip_lon,
|
304 |
+
"isp_name": isp_name,
|
305 |
+
"isp_org": isp_org,
|
306 |
+
"is_vpn": is_vpn,
|
307 |
+
"is_proxy": is_proxy,
|
308 |
+
"is_tor": is_tor,
|
309 |
+
"is_relay": is_relay,
|
310 |
+
"lat": lat,
|
311 |
+
"lon": lon,
|
312 |
+
"suburb": suburb,
|
313 |
+
"district": district,
|
314 |
+
"city": city,
|
315 |
+
"country": country
|
316 |
+
}
|
317 |
+
|
318 |
+
return user_dict, str(location)
|
319 |
+
|
320 |
+
# Retrieve login history of the user
|
321 |
+
def get_login_history(username):
|
322 |
+
|
323 |
+
login_time_history = []
|
324 |
+
# typing_speed_history = []
|
325 |
+
device_name_history = []
|
326 |
+
device_uuid_history = []
|
327 |
+
mac_address_history = []
|
328 |
+
device_vendor_history = []
|
329 |
+
device_model_history = []
|
330 |
+
device_ram_history = []
|
331 |
+
ip_v4_history = []
|
332 |
+
ip_country_history = []
|
333 |
+
ip_region_history = []
|
334 |
+
ip_city_history = []
|
335 |
+
ip_lat_history = []
|
336 |
+
ip_lon_history = []
|
337 |
+
isp_name_history = []
|
338 |
+
isp_org_history = []
|
339 |
+
is_vpn_history = []
|
340 |
+
is_proxy_history = []
|
341 |
+
is_tor_history = []
|
342 |
+
is_relay_history = []
|
343 |
+
lat_history = []
|
344 |
+
lon_history = []
|
345 |
+
suburb_history = []
|
346 |
+
district_history = []
|
347 |
+
city_history = []
|
348 |
+
country_history = []
|
349 |
+
|
350 |
+
login_data = get_login_data(username)
|
351 |
+
|
352 |
+
if login_data:
|
353 |
+
|
354 |
+
for row in login_data:
|
355 |
+
login_time_history.append(row[2])
|
356 |
+
# typing_speed_history.append(row[3])
|
357 |
+
device_name_history.append(row[3])
|
358 |
+
device_uuid_history.append(row[4])
|
359 |
+
mac_address_history.append(row[5])
|
360 |
+
device_vendor_history.append(row[6])
|
361 |
+
device_model_history.append(row[7])
|
362 |
+
device_ram_history.append(row[8])
|
363 |
+
ip_v4_history.append(row[9])
|
364 |
+
ip_country_history.append(row[10])
|
365 |
+
ip_region_history.append(row[11])
|
366 |
+
ip_city_history.append(row[12])
|
367 |
+
ip_lat_history.append(row[13])
|
368 |
+
ip_lon_history.append(row[14])
|
369 |
+
isp_name_history.append(row[15])
|
370 |
+
isp_org_history.append(row[16])
|
371 |
+
is_vpn_history.append(row[17])
|
372 |
+
is_proxy_history.append(row[18])
|
373 |
+
is_tor_history.append(row[19])
|
374 |
+
is_relay_history.append(row[20])
|
375 |
+
lat_history.append(row[21])
|
376 |
+
lon_history.append(row[22])
|
377 |
+
suburb_history.append(row[23])
|
378 |
+
district_history.append(row[24])
|
379 |
+
city_history.append(row[25])
|
380 |
+
country_history.append(row[26])
|
381 |
+
|
382 |
+
return login_time_history,\
|
383 |
+
device_name_history, device_uuid_history, mac_address_history, device_vendor_history, device_model_history, device_ram_history,\
|
384 |
+
ip_v4_history, ip_country_history, ip_region_history, ip_city_history, ip_lat_history, ip_lon_history, isp_name_history, isp_org_history,\
|
385 |
+
is_vpn_history, is_proxy_history, is_tor_history, is_relay_history,\
|
386 |
+
lat_history, lon_history, suburb_history, district_history, city_history, country_history
|
387 |
+
|
388 |
+
def submit_test_case(user_dict, location):
|
389 |
+
|
390 |
+
submit_button = st.button("Start test case")
|
391 |
+
|
392 |
+
# When clicking submit button
|
393 |
+
if submit_button:
|
394 |
+
|
395 |
+
# Call function to verify test case with historical data
|
396 |
+
verification = verify_user(user_dict)
|
397 |
+
|
398 |
+
# If fail the user verification logic
|
399 |
+
# if not(verification):
|
400 |
+
|
401 |
+
# # Check face verification
|
402 |
+
# verification = verify_face(user_dict.get('username', ''))
|
403 |
+
|
404 |
+
# Update location, user_dict to pass to other pages
|
405 |
+
st.session_state['location'] = location
|
406 |
+
st.session_state['user_dict'] = user_dict
|
407 |
+
st.session_state['verification'] = verification
|
408 |
+
|
409 |
+
# If passed all verification logic
|
410 |
+
if verification:
|
411 |
+
|
412 |
+
# Open Sent Page
|
413 |
+
switch_page("TestPass")
|
414 |
+
|
415 |
+
else:
|
416 |
+
|
417 |
+
# Open Failed Page
|
418 |
+
switch_page("InputImage")
|
419 |
+
|
420 |
+
def show_test_data(user_dict, location):
|
421 |
+
|
422 |
+
# Current information
|
423 |
+
username, login_time,\
|
424 |
+
device_name, device_uuid, mac_address, device_vendor, device_model, device_ram,\
|
425 |
+
ip_v4, ip_country, ip_region, ip_city, ip_lat, ip_lon, isp_name, isp_org,\
|
426 |
+
is_vpn, is_proxy, is_tor, is_relay,\
|
427 |
+
lat, lon, suburb, district, city, country = get_from_user_dict(user_dict)
|
428 |
+
|
429 |
+
# Show location
|
430 |
+
st.write('Location:', location)
|
431 |
+
|
432 |
+
col1, col2 = st.columns(2)
|
433 |
+
|
434 |
+
with col1:
|
435 |
+
|
436 |
+
# Show IP IP info
|
437 |
+
st.write('IP address:', ip_v4)
|
438 |
+
st.write('IP region:', ip_region)
|
439 |
+
st.write('IP city:', ip_city)
|
440 |
+
st.write('IP country:', ip_country)
|
441 |
+
st.write('Is VPN?', is_vpn)
|
442 |
+
st.write('Is Proxy?', is_proxy)
|
443 |
+
st.write('Is Tor Node?', is_tor)
|
444 |
+
st.write('Is Relay?', is_relay)
|
445 |
+
|
446 |
+
with col2:
|
447 |
+
|
448 |
+
# Show Device
|
449 |
+
st.write('ISP Name:', isp_name)
|
450 |
+
st.write('ISP Organisation:', isp_org)
|
451 |
+
st.write('Device Mac Address:', mac_address)
|
452 |
+
st.write('Device UUID:', device_uuid)
|
453 |
+
st.write('Device Name:', device_name)
|
454 |
+
st.write('Device Vendor:', device_vendor)
|
455 |
+
st.write('Device Model:', device_model)
|
456 |
+
st.write('Device Ram:', device_ram)
|
457 |
+
|
458 |
+
# Show Login time
|
459 |
+
st.write('Login time:', login_time)
|
460 |
+
|
461 |
+
def save_user_image(username, image, input_time):
|
462 |
+
save_dir = f"img/user_image/{username}"
|
463 |
+
save_file_path = f"img/user_image/{username}/{username}_" \
|
464 |
+
f"{int(input_time)}.jpg"
|
465 |
+
if not os.path.exists(save_dir):
|
466 |
+
os.makedirs(save_dir)
|
467 |
+
with open(save_file_path, mode='wb') as w:
|
468 |
+
w.write(image.getbuffer())
|
469 |
+
return save_file_path
|
470 |
+
|
471 |
+
def read_user_image(username):
|
472 |
+
list_image_path = []
|
473 |
+
image_dir = f"img/user_image/{username}"
|
474 |
+
if not os.path.exists(image_dir):
|
475 |
+
os.makedirs(image_dir)
|
476 |
+
for x in os.listdir(image_dir):
|
477 |
+
if x.split(".")[1].lower() in ("jpg", "png", "jpeg"):
|
478 |
+
image_path = os.path.join(image_dir, x)
|
479 |
+
list_image_path.append(image_path)
|
480 |
+
if list_image_path:
|
481 |
+
return max(list_image_path, key=os.path.getctime)
|
482 |
+
else:
|
483 |
+
return None
|
484 |
+
|
485 |
+
# @Thao: Here is to put user historical data verification logic to determine if this is the real user
|
486 |
+
# THAO LE CODE
|
487 |
+
|
488 |
+
# 0.5 to get the more suitable per
|
489 |
+
# get all the values with count in range ~20% less than the highest
|
490 |
+
def get_right_per(user_dict,user_db,match_value,per):
|
491 |
+
if match_value == "":
|
492 |
+
return 0
|
493 |
+
range_highest = 0.2
|
494 |
+
count_all = user_db.groupby(match_value).count().login_time
|
495 |
+
up = count_all.max()
|
496 |
+
down = up*(1-range_highest)
|
497 |
+
# print(f'range is from {down} to {up}')
|
498 |
+
if count_all[user_dict[match_value]] >= down and count_all[user_dict[match_value]] <= up:
|
499 |
+
return 1
|
500 |
+
else:
|
501 |
+
return per
|
502 |
+
|
503 |
+
# 1.1 to get the match value and percentage
|
504 |
+
def check_match_per(user_dict,user_db,check = 'location'):
|
505 |
+
"""
|
506 |
+
input the check is one of 'location','device','ip'
|
507 |
+
"""
|
508 |
+
match_value = ''
|
509 |
+
final_per = 0
|
510 |
+
per = 0
|
511 |
+
total_txn = len(user_db)
|
512 |
+
|
513 |
+
if check == 'location':
|
514 |
+
fields_check = ['country','city', 'district','suburb']
|
515 |
+
elif check == 'device':
|
516 |
+
fields_check = ['device_vendor','device_model','device_name','mac_address','device_uuid']
|
517 |
+
else:
|
518 |
+
fields_check = [ 'ip_country', 'isp_name','ip_v4']
|
519 |
+
|
520 |
+
for i in fields_check:
|
521 |
+
# print(user_db)
|
522 |
+
# print(user_dict[i])
|
523 |
+
count = len(user_db[user_db[i] == user_dict[i]])
|
524 |
+
if count > 0:
|
525 |
+
# if user[i] in user_db[i].values and user[i] != '':
|
526 |
+
match_value = i
|
527 |
+
per = count/total_txn
|
528 |
+
|
529 |
+
elif i == 'mac_address' and match_value != i:
|
530 |
+
continue
|
531 |
+
else:
|
532 |
+
break
|
533 |
+
|
534 |
+
final_per = get_right_per(user_dict,user_db,match_value,per)
|
535 |
+
# print('match value ',{match_value})
|
536 |
+
return match_value,final_per
|
537 |
+
|
538 |
+
# 2.1 Get velocity of all transactions
|
539 |
+
def get_vel_all(row):
|
540 |
+
# print(row)
|
541 |
+
dist = 0
|
542 |
+
coor = (row['lat'],row['lon'])
|
543 |
+
coor_pre = (row['pre_lat'],row['pre_lon'])
|
544 |
+
interval = ''
|
545 |
+
vel = ''
|
546 |
+
if coor_pre != (0,0):
|
547 |
+
dist = GD(coor,coor_pre).km
|
548 |
+
interval = (row['login_time'] - row['pre_time']).total_seconds()/(60*60)
|
549 |
+
# interval = (row['login_time'] - row['pre_time']).days
|
550 |
+
if interval != 0:
|
551 |
+
vel = dist/interval
|
552 |
+
else:
|
553 |
+
vel = 0
|
554 |
+
return vel
|
555 |
+
|
556 |
+
# 2.2 get vel of the latest txn
|
557 |
+
def get_vel_txn(user_dict,user_db):
|
558 |
+
"""
|
559 |
+
get the velocity of the new transaction in user_dict and the latest transaction in user's history
|
560 |
+
"""
|
561 |
+
dist = 0
|
562 |
+
interval = ''
|
563 |
+
latest_txn = user_db.iloc[[-1]]
|
564 |
+
# print(latest_txn)
|
565 |
+
coor_txn = (user_dict['lat'],user_dict['lon'])
|
566 |
+
coor_latest = (float(latest_txn['lat']),float(latest_txn['lon']))
|
567 |
+
dist = GD(coor_txn,coor_latest).km
|
568 |
+
|
569 |
+
print(type(user_dict['login_time']))
|
570 |
+
try:
|
571 |
+
time_txn = user_dict['login_time']
|
572 |
+
interval = (time_txn - latest_txn['login_time'].to_list()[0]).total_seconds()/(60*60)
|
573 |
+
except:
|
574 |
+
time_txn = datetime.strptime(user_dict['login_time'],'%Y-%m-%d %H:%M:%S')
|
575 |
+
interval = (time_txn - latest_txn['login_time'].to_list()[0]).total_seconds()/(60*60)
|
576 |
+
|
577 |
+
# if type(user_dict['login_time']) == 'str':
|
578 |
+
# time_txn = datetime.strptime(user_dict['login_time'],'%Y-%m-%d %H:%M:%S')
|
579 |
+
# else:
|
580 |
+
# time_txn = user_dict['login_time']
|
581 |
+
|
582 |
+
|
583 |
+
# print(latest_txn['login_time'])
|
584 |
+
# interval = (time_txn - latest_txn['login_time'].to_list()[0]).total_seconds()/(60*60)
|
585 |
+
try:
|
586 |
+
vel = dist/interval
|
587 |
+
except:
|
588 |
+
vel = 0
|
589 |
+
print(f'This is vel {vel}')
|
590 |
+
return(vel)
|
591 |
+
|
592 |
+
# 2 to get score will be reduced because of jumping
|
593 |
+
def get_score_jump (user_dict,user_db):
|
594 |
+
threshold_vel = {
|
595 |
+
'H':600,
|
596 |
+
'M':80,
|
597 |
+
'L':40,
|
598 |
+
'frequency':0.1
|
599 |
+
}
|
600 |
+
weight_vel = {
|
601 |
+
'H':20,
|
602 |
+
'M':10,
|
603 |
+
"L":5
|
604 |
+
}
|
605 |
+
many_vel = {
|
606 |
+
'Y':0.1,
|
607 |
+
'N':1
|
608 |
+
}
|
609 |
+
|
610 |
+
# to get how many jumping - low or not
|
611 |
+
count_jump = len(user_db[user_db.apply(lambda x: float(x.vel) > threshold_vel['L'] if x.vel != "" else False,axis=1)])
|
612 |
+
|
613 |
+
if count_jump > len(user_db)*threshold_vel['frequency']:
|
614 |
+
many_jump = 'Y'
|
615 |
+
else:
|
616 |
+
many_jump = 'N'
|
617 |
+
|
618 |
+
vel_txn = float(get_vel_txn(user_dict,user_db))
|
619 |
+
|
620 |
+
if vel_txn > threshold_vel['H']:
|
621 |
+
score_jump = many_vel[many_jump] * weight_vel['H']
|
622 |
+
elif vel_txn > threshold_vel['M']:
|
623 |
+
score_jump = many_vel[many_jump] * weight_vel['M']
|
624 |
+
elif vel_txn > threshold_vel['L']:
|
625 |
+
score_jump = many_vel[many_jump] * weight_vel['L']
|
626 |
+
else:
|
627 |
+
score_jump = 0
|
628 |
+
return(score_jump)
|
629 |
+
|
630 |
+
# 3.1 check vpn (new IP + vpn)
|
631 |
+
def get_vpn_score (user_dict,user_db):
|
632 |
+
vpn_fields = ['is_vpn','is_proxy', 'is_tor','is_relay']
|
633 |
+
weight_vpn = 10
|
634 |
+
vpn_count = 0
|
635 |
+
for i in vpn_fields:
|
636 |
+
vpn_count += user_dict[i]
|
637 |
+
if check_match_per(user_dict,user_db,check = 'ip')[0] != 'ip_v4' and vpn_count > 0:
|
638 |
+
return weight_vpn
|
639 |
+
else:
|
640 |
+
return 0
|
641 |
+
|
642 |
+
# 4.Get score
|
643 |
+
def get_risk_score (user_dict,user_db):
|
644 |
+
weight = {'device_uuid': 40, 'mac_address': 40, 'device_name': 30.0, 'device_model': 20.0, \
|
645 |
+
'device_vendor': 4.0, 'ip_v4': 30, 'isp_name': 15.0, 'ip_country': 3.0, 'suburb': 30, 'district': 22.5, \
|
646 |
+
'city': 15.0, 'country': 3.0}
|
647 |
+
device_match,device_per = check_match_per(user_dict,user_db,check='device')
|
648 |
+
if device_match != '':
|
649 |
+
device_score = weight[device_match] * device_per
|
650 |
+
else:
|
651 |
+
device_score = 0
|
652 |
+
|
653 |
+
ip_match,ip_per = check_match_per(user_dict,user_db,check='ip')
|
654 |
+
if ip_match != '':
|
655 |
+
ip_score = weight[ip_match] * ip_per
|
656 |
+
else:
|
657 |
+
ip_score = 0
|
658 |
+
|
659 |
+
# check location
|
660 |
+
location_match,location_per = check_match_per(user_dict,user_db,check='location')
|
661 |
+
if location_match != '':
|
662 |
+
location_score = weight[location_match] * location_per
|
663 |
+
else:
|
664 |
+
location_score = 0
|
665 |
+
# print(f'match location {location_match} with score {location_score}')
|
666 |
+
jump_score = get_score_jump (user_dict,user_db)
|
667 |
+
vpn_score = get_vpn_score (user_dict,user_db)
|
668 |
+
|
669 |
+
print(f'device score {device_score}')
|
670 |
+
print(f'ip_score {ip_score}')
|
671 |
+
print(f'location_score {location_score}')
|
672 |
+
print(f'jump_score {jump_score}')
|
673 |
+
print(f'vpn_score {vpn_score}')
|
674 |
+
|
675 |
+
# return device_score+ip_score+location_score-(jump_score + vpn_score)
|
676 |
+
total_score = device_score+ip_score+location_score-(jump_score + vpn_score)
|
677 |
+
score_dict = {
|
678 |
+
"device_score": device_score,
|
679 |
+
"ip_score": ip_score,
|
680 |
+
"location_score": location_score,
|
681 |
+
"jump_score": jump_score,
|
682 |
+
"vpn_score": vpn_score,
|
683 |
+
"total_score": total_score
|
684 |
+
}
|
685 |
+
st.session_state['score_dict'] = score_dict
|
686 |
+
|
687 |
+
return total_score
|
688 |
+
|
689 |
+
# User verification
|
690 |
+
def verify_user(user_dict):
|
691 |
+
|
692 |
+
verification = False
|
693 |
+
|
694 |
+
# Current information
|
695 |
+
# user_dict is dictionary
|
696 |
+
username, login_time,\
|
697 |
+
device_name, device_uuid, mac_address, device_vendor, device_model, device_ram,\
|
698 |
+
ip_v4, ip_country, ip_region, ip_city, ip_lat, ip_lon, isp_name, isp_org,\
|
699 |
+
is_vpn, is_proxy, is_tor, is_relay,\
|
700 |
+
lat, lon, suburb, district, city, country = get_from_user_dict(user_dict)
|
701 |
+
|
702 |
+
# Retrieve login history of the user - a tuple
|
703 |
+
# login_time_history, \
|
704 |
+
# device_name_history, device_uuid_history, mac_address_history, device_vendor_history, device_model_history, device_ram_history,\
|
705 |
+
# ip_v4_history, ip_country_history, ip_region_history, ip_city_history, ip_lat_history, ip_lon_history, isp_name_history, isp_org_history,\
|
706 |
+
# is_vpn_history, is_proxy_history, is_tor_history, is_relay_history,\
|
707 |
+
# lat_history, lon_history, suburb_history, district_history, city_history, country_history = get_login_history(username)
|
708 |
+
|
709 |
+
#Thao Le note:
|
710 |
+
col = ['login_time','device_name', 'device_uuid','mac_address', 'device_vendor', 'device_model', 'device_ram',\
|
711 |
+
'ip_v4','ip_country', 'ip_region', 'ip_city', 'ip_lat', 'ip_lon', 'isp_name','isp_org',\
|
712 |
+
'is_vpn', 'is_proxy', 'is_tor', 'is_relay', \
|
713 |
+
'lat', 'lon','suburb', 'district', 'city', 'country']
|
714 |
+
df = get_login_history(username)
|
715 |
+
|
716 |
+
user_db = pd.DataFrame(get_login_history(username)).T
|
717 |
+
user_db.columns= col
|
718 |
+
# print(f'this is from BAAM function, username is {username}')
|
719 |
+
# print('this is user_dict',user_dict)
|
720 |
+
if len(user_db) == 0:
|
721 |
+
print('This is the 1st login time of this username')
|
722 |
+
verification = True
|
723 |
+
score_dict = {}
|
724 |
+
st.session_state['score_dict'] = score_dict
|
725 |
+
return verification
|
726 |
+
|
727 |
+
# print(user_db)
|
728 |
+
# 2. Def to check score_jumping: velocity (H: 600+, M: 80 - 600, S: 40<80) and frequency_jumping (rare or ussually)
|
729 |
+
user_db.login_time = pd.to_datetime(user_db.login_time)
|
730 |
+
user_db['pre_lat'] = user_db['lat'].shift(periods=1, fill_value=0)
|
731 |
+
user_db['pre_lon'] = user_db['lon'].shift(periods=1, fill_value=0)
|
732 |
+
user_db['pre_time'] = user_db['login_time'].shift(periods=1,fill_value=0)
|
733 |
+
user_db['vel'] = user_db.apply(lambda x: get_vel_all(x),axis=1)
|
734 |
+
|
735 |
+
trust_score =get_risk_score (user_dict,user_db)
|
736 |
+
risk_threshold = 30
|
737 |
+
st.session_state['risk_threshold'] = risk_threshold
|
738 |
+
print(f'trust_score is {trust_score}')
|
739 |
+
if trust_score < risk_threshold:
|
740 |
+
verification = False
|
741 |
+
else:
|
742 |
+
verification = True
|
743 |
+
print(f'verification {verification}')
|
744 |
+
|
745 |
+
|
746 |
+
# verification = True # This should be removed after @Thao adds the function user historical data verification
|
747 |
+
|
748 |
+
return verification
|
749 |
+
|
750 |
+
# @Dora: This is to put the function face verification
|
751 |
+
# Face verification
|
752 |
+
def verify_face(username, img_file_buffer):
|
753 |
+
face_verification = True
|
754 |
+
# input_time = datetime.now().timestamp()
|
755 |
+
# latest_history_image = read_user_image(username)
|
756 |
+
# if latest_history_image:
|
757 |
+
# image_path = save_user_image(username, img_file_buffer, input_time)
|
758 |
+
# face_verification = face_verfication([[latest_history_image,image_path]])
|
759 |
+
# os.remove(image_path)
|
760 |
+
# else:
|
761 |
+
# face_verification = True
|
762 |
+
# if face_verification:
|
763 |
+
# image_path = save_user_image(username, img_file_buffer, input_time)
|
764 |
+
# print(f"verify face:{face_verification}")
|
765 |
+
|
766 |
+
return face_verification
|
backup-data.db
ADDED
Binary file (24.6 kB). View file
|
|
data.db
ADDED
Binary file (238 kB). View file
|
|
demo_user_flow.jpg
ADDED
![]() |
demo_user_profile.jpg
ADDED
![]() |
dummy_db.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
face_verification/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
face_verification/image/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
face_verification/image/charlie-puth.jpg
ADDED
![]() |
face_verification/image/iu.jpeg
ADDED
![]() |
face_verification/image/lilitran2.JPG
ADDED
|
face_verification/image/linhvuu.jpg
ADDED
![]() |
face_verification/image/linhvuu2.png
ADDED
![]() |
face_verification/image/linhvuu3.JPG
ADDED
|
face_verification/test_deepface.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|
face_verification_helper.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from deepface import DeepFace
|
2 |
+
|
3 |
+
DISTANCE_METRIC = "cosine" # cosine, euclidean, euclidean_l2
|
4 |
+
MODEL_NAME = 'ArcFace' # VGG-Face, Facenet, OpenFace, DeepFace, DeepID, Dlib, ArcFace or Ensemble
|
5 |
+
DETECTOR_BACKEND = "opencv" # Retinaface, mtcnn, opencv, ssd or dlib
|
6 |
+
|
7 |
+
|
8 |
+
def face_verfication(image_array):
|
9 |
+
"""
|
10 |
+
Checking user's face image with user face history images
|
11 |
+
If one check is True -> True
|
12 |
+
"""
|
13 |
+
result = False
|
14 |
+
verifications = DeepFace.verify(img1_path=image_array,
|
15 |
+
model_name=MODEL_NAME,
|
16 |
+
distance_metric=DISTANCE_METRIC,
|
17 |
+
detector_backend=DETECTOR_BACKEND)
|
18 |
+
for k, verification in verifications.items():
|
19 |
+
if verification["verified"]:
|
20 |
+
result = True
|
21 |
+
break
|
22 |
+
|
23 |
+
return result
|
24 |
+
|
25 |
+
|
img/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
img/Standard_Chartered.png
ADDED
![]() |
img/TestFail.png
ADDED
![]() |
img/TestPass.png
ADDED
![]() |
img/bank_sidebar.png
ADDED
![]() |
img/failed.png
ADDED
![]() |
img/failed_da.png
ADDED
![]() |
img/home.png
ADDED
![]() |
img/logo.png
ADDED
![]() |
img/menu.png
ADDED
![]() |
img/otp.jpg
ADDED
![]() |