Skip to content

Commit

Permalink
Google Authenticator in Python
Browse files Browse the repository at this point in the history
Google Authenticator desktop GUI and script application in Python with JSON secrets.
  • Loading branch information
atomjoy committed Feb 23, 2024
1 parent 321ac53 commit 5ad51f5
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 55 deletions.
1 change: 1 addition & 0 deletions backup/secrets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "atomjoy_github": "JBSWY3DPEHPK3PXP", "username_github": "A7SWY3DPEHPK3PXD" }
91 changes: 91 additions & 0 deletions json_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/python3

import pyotp, time, json, os, shutil, base64


class JsonFile:
data = []
data_code = []
filename = ""

def __init__(self, filename="secrets.json"):
self.data = []
self.data_code = []
self.filename = filename
self.__loadJson()
self.__updateCode()

def getAll(self):
return self.data_code

def getAllData(self):
return self.data

def loadJson(self):
try:
rel = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
with open(os.path.join(rel, self.filename), "r") as f:
secrets = json.load(f)
self.data = list(secrets.items())
print("Loaded", self.data)
except (ImportError, Exception):
print("Load error")

def addItem(self, name, secret):
if len(name) >= 3:
if len(secret) >= 16:
if self.isBase32(secret):
item = tuple([name, secret])
self.data.append(item)
self.__updateCode()
self.saveJson()
print("Appended", self.data)

def updateCode(self):
self.data_code = []
for tuple_val in self.data:
item = list(tuple_val)
item.append(self.otpCode(str(item[1])))
self.data_code.append(tuple(item))

def otpCode(self, secret):
try:
return str(pyotp.TOTP(secret).now())
except Exception:
print("Otp code error")
return str("000000")

def saveJson(self):
json_obj = {}
for key, secret in self.data:
json_obj[key] = secret
try:
self.backupFile()
rel = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
with open(os.path.join(rel, self.filename), "w") as outfile:
json.dump(json_obj, outfile)
print("Saved json", json_obj)
except (ImportError, Exception):
print("Save error")

def backupFile(self):
tm = str(time.time()).replace(".", "_")
shutil.copy(self.filename, "backup/secrets_copy_" + tm + ".json")

def isBase32(self, str):
try:
base64.b32decode(str)
return True
except Exception:
print("Invalid base32:", str)
return False

def removeItem(self, name):
if len(name) >= 3:
res = [i for i in self.data if i[0] != name]
self.data = res
self.saveJson()
print(res)

__loadJson = loadJson # private copy of original update() method
__updateCode = updateCode # private copy of original update() method
134 changes: 79 additions & 55 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/python3

import pyotp, time, json, os
import pyotp, time, json, os, shutil, base64
import tkinter
from tkinter import ttk
from tkinter import messagebox
from json_file import JsonFile

window = tkinter.Tk()
window.title("2FA Google Authenticator")
Expand All @@ -13,44 +14,40 @@
fr = tkinter.Frame(window)
fr.pack()


# Callbacks
def load_data():
rel = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
with open(os.path.join(rel,'secrets.json'), 'r') as f:
secrets = json.load(f)
items = list(secrets.items())
code_items = []
for tuple_val in items:
item = list(tuple_val)
item.append(pyotp.TOTP(item[1]).now())
code_items.append(tuple(item))
print(code_items)
return code_items
jf = JsonFile("secrets.json")
return jf.getAll()


def selectItem(event):
tree = event.widget
current_item = tree.focus()
item = tree.item(current_item)
print("Item", item)
if hasattr(item, 'values'):
try:
messagebox.showinfo('Current code', f'{item["values"][2]}')
print("Id:", current_item, "Item:", item, "Code:", item["values"][2])
except IndexError:
print('')
tree = event.widget
current_item = tree.focus()
item = tree.item(current_item)
print("Selected Item", item)
try:
if hasattr(item, "values"):
code = format(item["values"][2], "006d")
messagebox.showinfo("Code", code)
print("Id:", current_item, "Item:", item, "Code:", code)
except IndexError:
print("Empty item values", item)


def update():
t=time.strftime('%I:%M:%S', time.localtime())
treeview.delete(*treeview.get_children())
i = 0
for item_tuple in load_data():
treeview.insert('', tkinter.END, id=i, values=item_tuple)
i = i + 1
window.after(30000, update)
print("Update", t)
t = time.strftime("%I:%M:%S", time.localtime())
treeview.delete(*treeview.get_children())
i = 0
for item_tuple in load_data():
treeview.insert("", tkinter.END, id=i, values=item_tuple)
i = i + 1
window.after(15000, update)
print("Update", t)


# Show user codes
fr_codes = tkinter.LabelFrame(fr, text="2FA Codes", font='times 21')
fr_codes = tkinter.LabelFrame(fr, text="2FA Codes", font="times 21")
fr_codes.grid(row=0, column=0)

treeFrame = ttk.Frame(fr_codes)
Expand All @@ -59,47 +56,74 @@ def update():
treeScroll.pack(side="right", fill="y")

cols = ("Name", "Secret", "Code")
treeview = ttk.Treeview(treeFrame, show="headings", yscrollcommand=treeScroll.set, columns=cols, height=13, selectmode="browse")
treeview = ttk.Treeview(
treeFrame,
show="headings",
yscrollcommand=treeScroll.set,
columns=cols,
height=13,
selectmode="browse",
)
treeview.column("Name", width=250)
treeview.bind('<<TreeviewSelect>>', selectItem)
treeview.bind("<<TreeviewSelect>>", selectItem)
# treeview.bind('<ButtonRelease-1>', selectItem)

for col_name in cols:
treeview.heading(col_name, text=col_name)
treeview.heading(col_name, text=col_name)

i = 0

for item_tuple in load_data():
treeview.insert('', tkinter.END, id=i, values=item_tuple)
i = i + 1
treeview.insert("", tkinter.END, id=i, values=item_tuple)
i = i + 1

treeview.pack()
treeScroll.config(command=treeview.yview)

# # Saving user secret callbacks
# def callback_button():
# name = input_name.get()
# secret = input_secret.get()
# print('Name', name, 'Secret', secret)
#
# # Saving user secret
# fr_add_secret = tkinter.LabelFrame(fr, text="Add secret", font='times 21')
# fr_add_secret.grid(row=1, column=0)

# label_name = tkinter.Label(fr_add_secret, text="App name")
# label_name.grid(row=0, column=0)
# Saving user secret callbacks
def callback_add():
name = input_name.get()
secret = input_secret.get()
input_name.delete(0, "end")
input_secret.delete(0, "end")
js = JsonFile("secrets.json")
if len(name) >= 3:
if len(secret) >= 16:
print("Name", name, "Secret", secret)
js.addItem(name, secret)
update()


def callback_del():
name = input_name.get()
input_name.delete(0, "end")
js = JsonFile("secrets.json")
js.removeItem(name)
update()


# Saving user secret
fr_add_secret = tkinter.LabelFrame(fr, text="Add secret", font="times 21")
fr_add_secret.grid(row=1, column=0)

label_name = tkinter.Label(fr_add_secret, text="App name")
label_name.grid(row=0, column=0)

label_secret = tkinter.Label(fr_add_secret, text="App secret")
label_secret.grid(row=0, column=1)

# label_secret = tkinter.Label(fr_add_secret, text="App secret")
# label_secret.grid(row=0, column=1)
input_name = tkinter.Entry(fr_add_secret)
input_name.grid(row=1, column=0)

# input_name = tkinter.Entry(fr_add_secret)
# input_name.grid(row=1, column=0)
input_secret = tkinter.Entry(fr_add_secret)
input_secret.grid(row=1, column=1)

# input_secret = tkinter.Entry(fr_add_secret)
# input_secret.grid(row=1, column=1)
button_add = tkinter.Button(fr_add_secret, text="Append", command=callback_add)
button_add.grid(row=2, column=1)

# button_add = tkinter.Button(fr_add_secret, text="Save", command=callback_button)
# button_add.grid(row=2, column=1)
button_del = tkinter.Button(fr_add_secret, text="Delete", command=callback_del)
button_del.grid(row=2, column=0)

update()

Expand Down
123 changes: 123 additions & 0 deletions main_mini.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/python3

import pyotp, time, json, os, shutil
import tkinter
from tkinter import ttk
from tkinter import messagebox
from json_file import JsonFile

jf = JsonFile("secrets.json")
jf.addItem("benny_github", "JBSWY3DPEHPK3PX3")
jf.saveJson()
print("Json file", jf.getAll())

window = tkinter.Tk()
window.title("2FA Google Authenticator")
# window.geometry('300x200')
# window.resizable(False, False)

fr = tkinter.Frame(window)
fr.pack()


# Callbacks
def load_data():
rel = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
with open(os.path.join(rel, "secrets.json"), "r") as f:
secrets = json.load(f)
items = list(secrets.items())
code_items = []
for tuple_val in items:
item = list(tuple_val)
item.append(pyotp.TOTP(item[1]).now())
code_items.append(tuple(item))
print(code_items)
return code_items


def selectItem(event):
tree = event.widget
current_item = tree.focus()
item = tree.item(current_item)
print("Item", item)
if hasattr(item, "values"):
try:
messagebox.showinfo("Current code", f'{item["values"][2]}')
print("Id:", current_item, "Item:", item, "Code:", item["values"][2])
except IndexError:
print("")


def update():
t = time.strftime("%I:%M:%S", time.localtime())
treeview.delete(*treeview.get_children())
i = 0
for item_tuple in load_data():
treeview.insert("", tkinter.END, id=i, values=item_tuple)
i = i + 1
window.after(30000, update)
print("Update", t)


# Show user codes
fr_codes = tkinter.LabelFrame(fr, text="2FA Codes", font="times 21")
fr_codes.grid(row=0, column=0)

treeFrame = ttk.Frame(fr_codes)
treeFrame.grid(row=0, column=0, pady=10)
treeScroll = ttk.Scrollbar(treeFrame)
treeScroll.pack(side="right", fill="y")

cols = ("Name", "Secret", "Code")
treeview = ttk.Treeview(
treeFrame,
show="headings",
yscrollcommand=treeScroll.set,
columns=cols,
height=13,
selectmode="browse",
)
treeview.column("Name", width=250)
treeview.bind("<<TreeviewSelect>>", selectItem)
# treeview.bind('<ButtonRelease-1>', selectItem)

for col_name in cols:
treeview.heading(col_name, text=col_name)

i = 0

for item_tuple in load_data():
treeview.insert("", tkinter.END, id=i, values=item_tuple)
i = i + 1

treeview.pack()
treeScroll.config(command=treeview.yview)

# # Saving user secret callbacks
# def callback_button():
# name = input_name.get()
# secret = input_secret.get()
# print('Name', name, 'Secret', secret)
#
# # Saving user secret
# fr_add_secret = tkinter.LabelFrame(fr, text="Add secret", font='times 21')
# fr_add_secret.grid(row=1, column=0)

# label_name = tkinter.Label(fr_add_secret, text="App name")
# label_name.grid(row=0, column=0)

# label_secret = tkinter.Label(fr_add_secret, text="App secret")
# label_secret.grid(row=0, column=1)

# input_name = tkinter.Entry(fr_add_secret)
# input_name.grid(row=1, column=0)

# input_secret = tkinter.Entry(fr_add_secret)
# input_secret.grid(row=1, column=1)

# button_add = tkinter.Button(fr_add_secret, text="Save", command=callback_button)
# button_add.grid(row=2, column=1)

update()

window.mainloop()

0 comments on commit 5ad51f5

Please sign in to comment.