Compare commits

..

2 Commits

Author SHA1 Message Date
56599bfdb3 Setup childprocess 2023-10-26 21:57:21 +05:30
546c09cca2 Implement Electron
- Run npm start / npm test for devmode
2023-10-25 22:53:46 +05:30
16 changed files with 2004 additions and 951 deletions

View File

@ -1,2 +0,0 @@
{
}

114
README.md
View File

@ -1,114 +1,2 @@
# Unnamed Password Manager
### Introduction
This is a password manager. Keep your passwords safe. :><br>
### Features:
- Fully local storage
- Symmetric AES encryption
- Multiple users
- Cross platform
- Web ui
## Working
Why use Unnamed Password Manager (UNPM)
### Before UNPM
"Never use common password"
- A wise person
```mermaid
flowchart LR
vw[Visit Website]
--> s[Sign up using unique password]
--> fp["Forgot Password 😭"]
vw[Visit Website]
--> sa[Sign up with common password]
--> gh[Get all your accounts hacked]
```
### After UNPM
```mermaid
flowchart LR
vw[Visit Website]
--> s[Sign up using unique password]
--> sp[Save password safely in UNPM]
vwa[Visit Website again]
--> gp[Get password using our web-app]
--> l[Login]
```
## FAQ
#### How to Backup?
- Check where AppData is standard stored if your OS is not mentioned here.
- Linux:
- - Backup the file at the location "~/.local/share/Unnamed_Password_Manager/\<USERNAME\>"
- - Paste it in the same location or follow the directions for the OS.
# Dev docs
## Rest API
##### WARNING:
Do NOT use this api for ANY reason EXCEPT if usage is ONLY local, i.e.,
this api is NOT built for usage over an external network and doing so
WILL NOT BE SECURE
###### Note:
Server throws 405 METHOD NOT ALLOWED if the url is mistyped
```mermaid
sequenceDiagram
autonumber
actor User
box App
participant Frontend
participant Backend
end
Note over User, Backend : LOGIN
User -->> Frontend : Wants to login
Frontend -->> Backend : { user_name : "...", password : "..." } to /login
Backend -->> Frontend : Success : http 200
Backend -->> Frontend : user_name / password not given : http 400
Backend -->> User : user_name / password is wrong : http 403
Note over User, Backend : LOGOUT
User -->> Frontend : Wants to logout
Frontend -->> Backend : /logout
Backend -->> Frontend : Success : http 200
Backend -->> User : Not logged in : http 403
Note over User, Backend : ADD USER
User -->> Frontend : Wants to add user
Frontend -->> Backend : { user_name : "...", password : "..." } to /add_user
Backend -->> Frontend : Success : http 200
Backend -->> Frontend : user_name / password not given : http 400
Backend -->> User : user_name is taken : http 403
Note over User, Backend : GET DATA
User -->> Frontend : field/ entry, etc.
Frontend -->> Backend : /get_data
Backend -->> User : Not logged in : http 403
Note over User, Backend : CHANGE PASSWORD
User -->> Frontend : Wants to change password
Frontend -->> Backend : { password : "..." } to /change_password
Backend -->> Frontend : password not given : http 400
Backend -->> User : Not logged in : http 403
Note over User, Backend : ADD ENTRY
User -->> Frontend : Wants to add entry
Frontend -->> Backend : entry data to /add_entry
Note over Frontend, Backend : entry data is { entry_name : "...", fields : "..." }
Note over Frontend, Backend : fields should be string({ field1 : "...", field2 : "...", ... })
Backend -->> Frontend : Entry data is not correct : http 400
Backend -->> User : Not logged in : http 403
Note over User, Backend : DELETE ENTRY
User -->> Frontend : Wants to delete entry
Frontend -->> Backend : { entry_name : "..." } to /delete_entry
Backend -->> Frontend : entry_name not given : http 400
Backend -->> User : Not logged in or entry_name does not exist : http 403
Note over User, Backend : EDIT ENTRY NAME
User -->> Frontend : Wants to change entry name
Frontend -->> Backend : { old_entry_name : "...", new_entry_name : "..." }
Backend -->> Frontend : Success : http 200
Backend -->> Frontend : old or new entry names not given : http 400
Backend -->> User : Old user name doesn't exist or the new one already exists
```
Hi

View File

@ -9,6 +9,9 @@ from data_handler import DataHandler
app: Flask = Flask(__name__)
if __name__ == "__main__":
app.run(debug=True)
def handle_first_launched():
"""

37
backend/rest_api.spec Normal file
View File

@ -0,0 +1,37 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['rest_api.py', 'encryption_handler.py', 'data_handler.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='rest_api',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

View File

View File

@ -3,6 +3,13 @@
<head>
<meta http-equiv="CONTENT-TYPE" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' http://127.0.0.1:5000;
style-src 'self' https://fonts.googleapis.com 'unsafe-inline';
script-src 'self' 'unsafe-inline';
script-src-elem 'self' 'unsafe-inline';
img-src 'self' http://www.google.com/s2/favicons https://t1.gstatic.com https://t0.gstatic.com https://t2.gstatic.com">
<title>DashBoard</title>
<link rel="stylesheet" href="../styles/dashboard.css"/>
</head>
@ -81,7 +88,7 @@
</div>
<div>
<label id="greetname"></label>
<a href="#" onclick="await changePassConfirm()">Change Password</a>
<a href="#" onclick="changePassConfirm()">Change Password</a>
</div>
</div>
@ -114,9 +121,9 @@
<input type="text" name="sitename" placeholder="Site Name" id="sitenamefield" >
<input type="text" name="url" placeholder="Site URL" id="urlfield" value="https://">
<label></label>
<button type="button" onclick="await validateData()" id="submitdatabtn"></button>
<button type="button" onclick="await deleteEntryConfirm()" id="deletedata"></button>
<button type="button" onclick="await validateDataEdit()" id="submiteditdatabtn">Save</button>
<button type="button" onclick="validateData()" id="submitdatabtn"></button>
<button type="button" onclick="deleteEntryConfirm()" id="deletedata"></button>
<button type="button" onclick="validateDataEdit()" id="submiteditdatabtn">Save</button>
</div>
<div class="inputfieldpanel">
@ -146,7 +153,7 @@
<div class="popupdivparent">
<div class="popupdiv">
<div>
<button onclick="await closePopup()" id="closepopup"></button>
<button onclick="closePopup()" id="closepopup"></button>
</div>
<div class="popupdivchild"></div>
</div>

View File

@ -3,6 +3,14 @@
<html>
<head>
<meta http-equiv="CONTENT-TYPE" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' http://127.0.0.1:5000;
style-src 'self' https://fonts.googleapis.com 'unsafe-inline';">
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<link rel="stylesheet" href="../styles/index.css"/>
<title>Login</title>
<link rel="preconnect" href="https://fonts.googleapis.com">

View File

@ -3,6 +3,12 @@
<html>
<head>
<meta http-equiv="CONTENT-TYPE" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' http://127.0.0.1:5000;
style-src 'self' https://fonts.googleapis.com 'unsafe-inline';
script-src 'self' 'unsafe-inline';
script-src-elem 'self' 'unsafe-inline'">
<link rel="stylesheet" href="../styles/register.css"/>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@ -29,7 +35,7 @@
</div>
<div class="buttons">
<button id="loginb" onclick="await validateRegister()">Sign Up</button>
<button id="loginb" onclick="validateRegister()">Sign Up</button>
</div>
</div>

77
frontend/main.js Normal file
View File

@ -0,0 +1,77 @@
const { app, BrowserWindow, shell } = require('electron')
const path = require('path')
const { spawn } = require('node:child_process');
const rest_api = spawn('python', ['./backend/rest_api.py']);
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
}
})
win.loadFile('frontend/html/index.html')
win.setMenu(null);
win.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
win.webContents.openDevTools();
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
rest_api.stdout.on('data', (data) => {
console.log(data.toString());
});
rest_api.stderr.on('data', (data) => {
console.error(data.toString());
});
rest_api.on('exit', (code) => {
console.log(`Child exited with code ${code}`);
});
});
app.on('before-quit', () => {
rest_api.kill();
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('web-contents-created', (event, contents) => {
contents.on('new-window', (event, navigationUrl) => {
event.preventDefault();
require('electron').shell.openExternal(navigationUrl);
});
});

View File

@ -115,10 +115,10 @@ function getDisplayList(fieldnames,userdata){
const search = document.getElementById('search');
search.addEventListener('keyup', async function() {
search.addEventListener('keyup', function() {
let result = search.value;
if(result == ""){
await getData()
getData()
} else {
getFilterList(result,JSON.parse(localStorage.getItem("userdata")))
}
@ -196,13 +196,13 @@ function addFieldLogic() {
//----------------------------------------------------------------------------------------- Form Data Logic
async function submitForm() {
function submitForm() {
const form = document.getElementById('formdata')
const formdata = new FormData(form)
await formatData(formdata)
formatData(formdata)
}
async function formatData(userdata) {
function formatData(userdata) {
const formData = new FormData()
const objData = {}
let sitename;
@ -216,7 +216,7 @@ async function formatData(userdata) {
formData.append("entry_name", sitename)
formData.append("fields", JSON.stringify(objData))
// console.log(sitename)
await sendData(formData)
sendData(formData)
}
//----------------------------------------------------------------------------------------- Post Entry Data to API
@ -235,7 +235,7 @@ async function sendData(formData){
entrywindow.style.display = "none"
localStorage.removeItem("editVal")
}, 1000);
await getData()
getData()
}
if(!response.ok){
const errorMessage = await response.text();
@ -252,9 +252,9 @@ async function sendData(formData){
//----------------------------------------------------------------------------------------- Form Data Validation Logic
async function validateData() {
function validateData() {
if(validateSitename() && validateUrl())
await submitForm();
submitForm();
return;
}
@ -358,9 +358,9 @@ function addFieldEditLogic(field){
//----------------------------------------------------------------------------------------- Edit Data Validation
async function validateDataEdit() {
function validateDataEdit() {
if(validateEditSitename() && validateEditUrl())
await editData();
editData();
return;
}
@ -410,8 +410,9 @@ async function deleteEntry() {
if(response.ok){
console.log(`Deleted ${localStorage.getItem("editVal")}`)
localStorage.removeItem("editVal")
await getData()
getData()
confirmation()
await delay(1000)
const infobox = document.querySelector('.infobox')
const infopanel = document.querySelector('.infopanel')
const popupdivparent = document.querySelector('.popupdivparent')
@ -442,7 +443,7 @@ function deleteEntryConfirm() {
const popupdivparent = document.querySelector('.popupdivparent')
const popupdiv = document.querySelector('.popupdivchild')
popupdivparent.style.display = "flex"
popupdiv.innerHTML = "<div class='c1f'><label>Are You Sure?</label></div>\n <div class='c1l'><button onclick='await deleteEntry()'>Yes</button> <button onclick='closePopup()'>No</button></div>"
popupdiv.innerHTML = "<div class='c1f'><label>Are You Sure?</label></div>\n <div class='c1l'><button onclick='deleteEntry()'>Yes</button> <button onclick='closePopup()'>No</button></div>"
}
//----------------------------------------------------------------------------------------- Change Pass Entry Popup
@ -451,10 +452,10 @@ function changePassConfirm() {
const popupdivparent = document.querySelector('.popupdivparent')
const popupdiv = document.querySelector('.popupdivchild')
popupdivparent.style.display = "flex"
popupdiv.innerHTML = "<div class='c2'><input id='c2i1' placeholder='Enter Password' type='password'></input><input id='c2i2' placeholder='Confirm Password' type='password'></input><label id='passerrlabel'></label><button id='changepassbtn' onclick='await changePass()'>Submit</button></div>"
popupdiv.innerHTML = "<div class='c2'><input id='c2i1' placeholder='Enter Password' type='password'></input><input id='c2i2' placeholder='Confirm Password' type='password'></input><label id='passerrlabel'></label><button id='changepassbtn' onclick='changePass()'>Submit</button></div>"
}
async function changePass() {
function changePass() {
const password = document.getElementById('c2i1').value
const confirmpassword = document.getElementById('c2i2').value
const errlabel = document.getElementById('passerrlabel')
@ -471,7 +472,7 @@ async function changePass() {
},3000)
}
else if(password == confirmpassword){
await sendPass(password)
sendPass(password)
}
}
@ -487,6 +488,7 @@ async function sendPass(password) {
if(response.ok){
console.log(`Password Changed!`)
confirmation()
await delay(1000)
const popupdivparent = document.querySelector('.popupdivparent')
const popupdiv = document.querySelector('.popupdivchild')
popupdivparent.style.display = "none"
@ -508,7 +510,7 @@ async function sendPass(password) {
//----------------------------------------------------------------------------------------- Edit Data Submission Logic
async function editData() {
function editData() {
const backbtn = document.getElementById('backbtn')
const submitbtn = document.getElementById('submiteditdatabtn')
const binbutton = document.getElementById('deletedata')
@ -517,7 +519,7 @@ async function editData() {
submitbtn.disabled = true
const form = document.getElementById('formdata')
const formeditdata = new FormData(form)
await editDataHandler(formeditdata)
editDataHandler(formeditdata)
}
let isError;
@ -551,19 +553,23 @@ async function editDataHandler(editdata) {
let value = objData[field]
if(!(field in userdata[entryname])){
await addNewField(entryname, field, value)
addNewField(entryname, field, value)
await delay(500)
}
else if(value != userdata[entryname][field]){
await editFieldValue(entryname, field, value )
editFieldValue(entryname, field, value )
await delay(500)
}
}
}
if(sitename != entryname){
await changeEntryName(entryname, sitename)
changeEntryName(entryname, sitename)
await delay(500)
}
await getData()
getData()
await delay(100)
if(!isError){
updateInfoGui(localStorage.getItem("editVal"))
confirmation()
@ -817,7 +823,7 @@ logoutbtn.addEventListener('click', () => {
window.onload = async function() {
await getData();
window.onload = function() {
getData();
greet()
};
};

View File

@ -30,9 +30,9 @@ function validatePass() {
}
}
async function validateLogin() {
function validateLogin() {
if(validateName() && validatePass()) {
await getAuth();
getAuth();
return;
}
}

View File

@ -1,7 +1,7 @@
const PORT = 5000
const errlabel = document.getElementById('errlabel')
async function validateRegister() {
function validateRegister() {
const username = document.getElementById('username').value
const password = document.getElementById('password').value
const confirmpassword = document.getElementById('conpassword').value
@ -19,7 +19,7 @@ async function validateRegister() {
}, 3000);
}
else if(password == confirmpassword && username!=''){
await createUser(username,password)
createUser(username,password)
}
}
@ -35,12 +35,12 @@ async function createUser(username,password) {
});
if(response.ok) {
console.log("User Created!")
await Login(username,password)
Login(username,password)
} else if(!response.ok) {
const errorMessage = await response.text();
errlabel.textContentL = errorMessage;
setTimeout(()=> {
errorlabel.textContent = "";
errlabel.textContent = "";
},3000)
throw new Error(errorMessage);
}
@ -75,4 +75,4 @@ async function Login(username,password){
} catch (error) {
console.error('Error:', error.message);
}
}
}

File diff suppressed because it is too large Load Diff

1023
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,18 @@
{
"name": "unnamed-password-manager",
"name": "password-manager",
"version": "1.0.0",
"description": "Hi",
"main": "index.js",
"description": "A Password Manager",
"main": "frontend/main.js",
"scripts": {
"start": ""
"test": "electronmon .",
"start": "electron ."
},
"keywords": [],
"author": "",
"license": "ISC"
"license": "MIT",
"dependencies": {
"electron": "^27.0.2"
},
"devDependencies": {
"electronmon": "^2.0.2"
}
}