Added libs

This commit is contained in:
Lucas
2026-01-25 13:55:46 +10:00
parent 575c682afc
commit f70af3c4ea
229 changed files with 26983 additions and 0 deletions

2
lockbox/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
__pycache__
lockbox.zip

1
lockbox/.mtimes.json Normal file
View File

@@ -0,0 +1 @@
{".gitignore": 1741162489.7420642, "api.http": 1750139941.0187607, "config.json": 1756106460.8736105, "lockboxClient.py": 1757415645.911955, "lockboxService.py": 1757490968.0438733, "lockboxServiceApi.py": 1757415119.6281, "miner.py": 1757325231.7886086, "minerApiBlueprint.py": 1757323989.6872394, "minerSocketio.py": 1757322038.733255, "requirements.txt": 1756036846.2401123, "userHistory.json": 1756014413.6882465, "utils.py": 1756014180.2178173, "browserExtension\\hello.html": 1740309144.9421954, "browserExtension\\hello_extensions.png": 1740305089.7709863, "browserExtension\\main.js": 1740309132.1250446, "browserExtension\\manifest.json": 1740308869.3939943, "browserExtension\\socketio.js": 1740305420.5022602, "frontend\\.gitignore": 1753127603.0183902, "frontend\\ca.pem": 1757146987.2444186, "frontend\\ca_key.pem": 1757146987.2449198, "frontend\\cert.pem": 1757146987.2669685, "frontend\\index.html": 1753127949.1451695, "frontend\\jsconfig.json": 1753127603.072253, "frontend\\key.pem": 1757146987.2669685, "frontend\\package-lock.json": 1753577901.326157, "frontend\\package.json": 1753264154.9925382, "frontend\\README.md": 1753127749.0499122, "frontend\\vite.config.js": 1753133376.4389508, "frontend\\webView.py": 1757273055.978937, "frontend\\.vscode\\extensions.json": 1753127603.0647097, "frontend\\.vscode\\settings.json": 1753127603.0858867, "frontend\\dist\\favicon.ico": 1753127603.0304024, "frontend\\dist\\index.html": 1757196785.0428426, "frontend\\dist\\assets\\index-BZcXFX0l.css": 1757196785.0428426, "frontend\\dist\\assets\\index-DLC-DzCB.js": 1757196785.043342, "frontend\\public\\favicon.ico": 1753127603.0304024, "frontend\\src\\App.vue": 1753578911.9599397, "frontend\\src\\main.js": 1753571177.8232467, "frontend\\src\\assets\\logo.svg": 1753127603.100169, "frontend\\src\\assets\\main.css": 1753134037.7614193, "frontend\\src\\components\\buttons\\Button.vue": 1754224356.4003878, "frontend\\src\\components\\buttons\\ToogleSwitch.vue": 1754224437.5005052, "frontend\\src\\components\\cards\\Card.vue": 1754222510.193816, "frontend\\src\\components\\cards\\CardContent.vue": 1753192461.7694707, "frontend\\src\\components\\cards\\CardDescription.vue": 1753138683.593848, "frontend\\src\\components\\cards\\CardFooter.vue": 1753138707.6460035, "frontend\\src\\components\\cards\\CardHeader.vue": 1753190956.6568618, "frontend\\src\\components\\cards\\CardTitle.vue": 1753147372.2295625, "frontend\\src\\components\\inputs\\InputText.vue": 1754224553.033484, "frontend\\src\\components\\labels\\Label.vue": 1753141147.9629083, "frontend\\src\\components\\tabs\\TabCreateUser.vue": 1754224498.9855795, "frontend\\src\\components\\tabs\\TabProofOfWork.vue": 1754224221.950291, "frontend\\src\\components\\tabs\\TabSigningRequests.vue": 1754224256.0454957, "frontend\\src\\components\\tabs\\TabUsers.vue": 1754224245.3672867, "frontend\\src\\plugins\\socketio.js": 1756116610.3355243, "frontend\\src\\stores\\auth.js": 1753260869.1848066, "frontend\\src\\views\\Home.vue": 1754224669.0649729, "vue\\dependencies.txt": 1757200759.1109006, "vue\\router.js": 1757197583.1419137, "vue\\api\\lockboxClientApi.js": 1757244004.148773, "vue\\api\\lockboxServiceApi.js": 1757198065.150491, "vue\\api\\socketEvents.js": 1757198163.0826175, "vue\\components\\tabs\\TabCreateUser.vue": 1757197963.0401194, "vue\\components\\tabs\\TabProofOfWork.vue": 1757496275.300655, "vue\\components\\tabs\\TabSigningRequests.vue": 1757829169.3566494, "vue\\components\\tabs\\TabUsers.vue": 1757496293.6970932, "vue\\stores\\auth.js": 1757202108.6815858, "vue\\views\\HomeView.vue": 1757243879.6857555, "__pycache__\\lockboxService.cpython-311.pyc": 1756014397.4503593, "__pycache__\\lockboxServiceApi.cpython-311.pyc": 1756014397.199929, "__pycache__\\miner.cpython-311.pyc": 1753400645.8070076, "__pycache__\\minerBlueprint.cpython-311.pyc": 1753578561.8273892, "__pycache__\\utils.cpython-311.pyc": 1756014259.7933002}

24
lockbox/api.http Normal file
View File

@@ -0,0 +1,24 @@
@hostname = http://127.0.0.1
@port = 5001
@hostServer = {{hostname}}:{{port}}
###
POST {{hostServer}}/users HTTP/1.1
content-type: application/json
{"password":"MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MTI=", "data":{"proof_of_work":"eYnU*@"}}
###{"password":"I0x1Y2FzR2FicmllbFZhekRvc1NhbnRvc0luYWNpbyE=", "data":{"proof_of_work":"eYnU*@"}}
### A4DZSk+TlR+4w39MbiIAQbti+N0H1QlJEhRH2DI6Iubj
###
GET {{hostServer}}/users
###
GET {{hostServer}}/users/A14%2FRek5z78U1rYC%2BWLvU%2FifnsX43o0tjnexmYdlXsjY
###
POST {{hostServer}}/users/A14%2FRek5z78U1rYC%2BWLvU%2FifnsX43o0tjnexmYdlXsjY/sign HTTP/1.1
content-type: application/json
{"data":"Test"}

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>Flask WebSocket Example</title>
</head>
<body style="background: #000;">
<input type="text" id="message">
<button id="btn">Send</button>
<span id="result" style="color: aliceblue;"></span>
</body>
<script src="socketio.js"></script>
<script src="main.js"></script>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

View File

@@ -0,0 +1,19 @@
var socket = io("http://127.0.0.1:5000");
socket.on('connect', function() {
console.log('Connected to server');
});
socket.on('message', function(msg) {
console.log('Message received: ' + msg);
document.getElementById("result").innerHTML = msg
});
socket.on('disconnect', function() {
console.log('Disconnected from server');
});
function sendMessage() {
var msg = document.getElementById('message').value;
socket.send(msg);
socket.emit('ola',msg);
}
document.getElementById("btn").addEventListener("click", sendMessage);

View File

@@ -0,0 +1,10 @@
{
"name": "Hello Extensions",
"description": "Base Level Extension",
"version": "1.0",
"manifest_version": 3,
"action": {
"default_popup": "hello.html",
"default_icon": "hello_extensions.png"
}
}

File diff suppressed because it is too large Load Diff

6
lockbox/config.json Normal file
View File

@@ -0,0 +1,6 @@
{
"server": {
"host": "https://localhost",
"port": "5001"
}
}

30
lockbox/frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

View File

@@ -0,0 +1,29 @@
# vue
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```

19
lockbox/frontend/ca.pem Normal file
View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDEzCCAfugAwIBAgIULnlTx9ReaTKzpVwe2WsLQUVqDiIwDQYJKoZIhvcNAQEL
BQAwOTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCE5vU3lzLUNBMRcwFQYDVQQDDA5O
b1N5cyBMb2NhbCBDQTAeFw0yNTA5MDYwODIzMDdaFw0zNTA5MDQwODIzMDdaMDkx
CzAJBgNVBAYTAlVTMREwDwYDVQQKDAhOb1N5cy1DQTEXMBUGA1UEAwwOTm9TeXMg
TG9jYWwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9RQvCGPLZ
z8V4qhM1IsUmSP3/05jEd9oBN5AmXIdiFHjlzBGVwgaqvhX7dWMAlaodN5s3moCu
His2NyhCikXv/MepkguWChsljmfqiVEUUN/ZVYaUzYwYMQ7KY1Pz1+qaB6dsOzsi
RFWv0AOZ5er3AIenpRF/3Y5yL3yqwzNcgEr5ULppCvIJdtyt9Da43p0zFnphGQL0
IIAUnp4mEyBypghMWsNn2RtnD7gxGFrcHWgilC+EG44MUGnDZ2jAdxOc/x8Mac/n
/VGCAYEGWIK/3lsAK1vdZMOQAXNpZC8zsPJFwNUXWQoJmZoqb9xyYmHU9VH/oc24
wt5Q6S1/MYJlAgMBAAGjEzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAKEm4Jv1KGkZJHl/nnalpbxoc6Rj2KsaWvsGgW4NyGk+rBKqfy9uhd9e
5DHoJ/KpLqQBL56+rU4oWu54/0CD0943oWoy6qroAphkJ3yhuRndKJgWvZHAyoIp
+ESzHkiYaf7UXMqO6zdC6CP1fJL6ap4eRokEhp4+y+g63KJowuZl6HKqNh6wQaW9
MqflZOP0rLBqDvbOPpSWUDZ77UKE4ZIRxarIas3KYyQtSBGBQjNXTysa7aCljGGv
yNhl3gQ7HZzchigPUtsdLgQRDI/wDcrwPPhlHfyy76M8gcP5Nsodn++rh1O+L+Ff
FCc+hSyCF6bajrkPWq/8WDCzC/XQs8k=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAvUULwhjy2c/FeKoTNSLFJkj9/9OYxHfaATeQJlyHYhR45cwR
lcIGqr4V+3VjAJWqHTebN5qArh4rNjcoQopF7/zHqZILlgobJY5n6olRFFDf2VWG
lM2MGDEOymNT89fqmgenbDs7IkRVr9ADmeXq9wCHp6URf92Oci98qsMzXIBK+VC6
aQryCXbcrfQ2uN6dMxZ6YRkC9CCAFJ6eJhMgcqYITFrDZ9kbZw+4MRha3B1oIpQv
hBuODFBpw2dowHcTnP8fDGnP5/1RggGBBliCv95bACtb3WTDkAFzaWQvM7DyRcDV
F1kKCZmaKm/ccmJh1PVR/6HNuMLeUOktfzGCZQIDAQABAoIBAAZ2GB5IHcGoujt0
Y4dSUANJu7PvjO0FBwlgD63PlIkjBGVCEQsAdMMxvyKFVDmD4nmryoe6G7NCTrVX
VmWq0k60DwBFplGbVVSHvQYgoEHjhiR+F1ex4ySybs/UQys1W7ZmlUKus+OO5OH2
yDPEZzYRdZAYGr+tk02PY3OZO25U2HbS7xZWz68xWQJPuEJLm4kckWX+ylv0vlEM
H7XcdNlAizGBp8wazQDL7kdfhnxZKUDTyaR4iWqC4qDT5bObPU4t7xiE43W8DH6C
DI22zxriSeAkjzRbFAv67ws+nEzDvm7Oshz0kD9WqK0U0nkcibrCLXckZHni4ewQ
HKzCAq0CgYEA4Tcpve20GhyCngEnk+MpUs7FP686+Ytrr7P65yRXEO8H1LPInD6J
w/KRP5cRtCgV/Q+pp6yTr5YQlSSR7evQy04pquEJ6423n4zQTjROpmGxIVNzeAv+
JwWpInLbEe5iafds+EXlAEo1buB5J47/LLP7LLcTOTtmLzjZzIPczgMCgYEA1yQN
k6yPx5AHqjXSSH3UK0QC/lIMDBHbKtk+eB6lobN/skY9Ggw41Ph5wOZlJU4VQ9q9
CsPtHr6izuxezmzbV0ae6a7dxM5fCSVs6V7IGNGIKF1xk6J0dqRbQxjv9Cid4mZM
ORDNMdDWnfiB+/hjJ8KbLxZVJeYG7rXzkZvilXcCgYAUTXjB2m/l+rP7snby6gOL
p4A4oX9bh6oJiNwRgkEnEaVPE3X+P9UDiRZ2+RNrfkGdMpBEwVX++jQ8fbN6E0wb
R8yRzv+p8HihNXyB0E1Wym/BZVh/dfVPZz88D8aX8zmD+/4i04o1YHs4p5vEaSuv
x/nYqhhdjHFFyIY53ZlGKQKBgGItRrDUN4y3Mng/NWX1XeQclk0effboEx77omFI
gwdGlYhyOyHu3+R4O3+G5DNg7Z1YbZpDDKtSDZPmE+GZlYK/bHdxYCyWjJHs7UWW
BjQlMkBRXComIYWevTLiZ2YBPwN48WG9RBZumfHe3NpyYDVdvll/lH3F+fXnPG5/
N6hDAoGARq6gWGk4IiZj9J0P8ZgKGZzWwnxcAxPm5JbbkhTGNtrkJMOAok6j/vDy
dq5i6XWXQg6jXlC8q6sNQfRjCZEzpdxlzsjpmeSMoKaqzTyQVTUQcdUtHedSPUDI
YyXJScUDIfGaRgC8UwF8c4vpYPmy1s+jGD3CyhMLM8kgeM+85Yc=
-----END RSA PRIVATE KEY-----

19
lockbox/frontend/cert.pem Normal file
View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDFjCCAf6gAwIBAgIUSdT3UOQDfikwJWhlc2sxUE6Vsi4wDQYJKoZIhvcNAQEL
BQAwOTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCE5vU3lzLUNBMRcwFQYDVQQDDA5O
b1N5cyBMb2NhbCBDQTAeFw0yNTA5MDYwODIzMDdaFw0zNTA5MDQwODIzMDdaMDEx
CzAJBgNVBAYTAlVTMQ4wDAYDVQQKDAVOb1N5czESMBAGA1UEAwwJbG9jYWxob3N0
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArg8PlencKCgepNGaRDx0
lr+2QdAzILSaPt8WNAYK9+cNHfUAhoWB2c47nZezKtE5tInrw/6Ubx0wIvm2IaWR
dN6TKUpBzrPru7hyxAGxKbefRVLhxt/O8rXxkJqBeCf5YByVZ4oGiEyeKyCt6umd
faLQMB3g7ZKGS6khjWayVCMNh2BMwdYZ8pEDvdms91hdtS6rQntM9foOA1vYmwBv
Z735NbymNGDEDHPx9lgBc6ZCL3gwNGvwEo/NNXubuYdZhwDtDytZTRfal1z1gE+7
4BcX01ZdkUgo8bs6Gp9b3JEY59ctHv2K9HdMRJ8imfVq11HwUaVMrF17YOt43j7Z
NwIDAQABox4wHDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcN
AQELBQADggEBAAC0HUlG0udKDtiZUHXQklDDGCw/07ohnoRwVQxba6FzIPe74ca6
j6Y7dKw9pXFy1h5scUyjS7Sl0AEkAalDh0wnedUwXPBtfrz5tzBUyOLJPtyFMCAV
JhXpCivwd++f0p3TtpDvDR6VQy79rqPqCwO6wQ4dx/62g9f2NKtTztNV5w74TEV0
Dx4OyFPAVoiEpqpTEuJ1v7HPYM8I4EcvE3jZSJDmzO1EhcxuJvUcJ1o1QBa5HW34
/hTvDDlg2w+++Zc8/VYVgt/kGpf7jjFaUml3jzNNpxm+KGYudb+pGNVyb/w6schj
FfKloHvLD2pAsCXo8VQ28VcaH42DEqeF10I=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lockbox</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

27
lockbox/frontend/key.pem Normal file
View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEArg8PlencKCgepNGaRDx0lr+2QdAzILSaPt8WNAYK9+cNHfUA
hoWB2c47nZezKtE5tInrw/6Ubx0wIvm2IaWRdN6TKUpBzrPru7hyxAGxKbefRVLh
xt/O8rXxkJqBeCf5YByVZ4oGiEyeKyCt6umdfaLQMB3g7ZKGS6khjWayVCMNh2BM
wdYZ8pEDvdms91hdtS6rQntM9foOA1vYmwBvZ735NbymNGDEDHPx9lgBc6ZCL3gw
NGvwEo/NNXubuYdZhwDtDytZTRfal1z1gE+74BcX01ZdkUgo8bs6Gp9b3JEY59ct
Hv2K9HdMRJ8imfVq11HwUaVMrF17YOt43j7ZNwIDAQABAoIBABoZyREGagydg4bc
pYDw/dyzL93rnhcb7ftakaZId7GX9KgW2rbRY1jpa5gkrOnRSRFxEyknTlPhMRw1
jOG7xbWcQL4S1A5ufX1/WbpZtJrYXapUFOYxHoPX07sG6D4/5E3My3ykvnkG4DsA
YgQVdxflZ8mnWVjWvYuv94eQLFKgVSIr6OtJYAhpofVY/qRHYjBg8RLgjZbmyp9R
QShlza0XHj4XZV5pXRrBWPWlF5ws7/OXHnCDxVq2xV1EQ9652HUTVtjPWiyfW4Ih
pZV9kuDU8HT54tFjoVhWrVY3+EFrV63Y+6uUWwKoB/P8jMtvo5QwSgl8pZL61YQD
RTo01oUCgYEA8TZ09fxkNgdH4XyQp+8FjdiBSlRx7XyXiE7dOfzxLaZ34fi85sgZ
tI0Aai8ouskJci9QoKk2WkcX8vY0cyiXbgXehCbPBdRnPSM7zDRtvURhaL40w/+G
VTcgJ/WIv/SwrmqL2jl56/ywrf/n+g1cN2bkCpNDIyXYY5wj34/e9mMCgYEAuLq0
X1lzGzbgZ99XRNinCxq8hw7KVZpGd21bZsopgz7tnlZ4v347iIUhnNN93DwffFr8
nhoVo5acW9DP8/xb7cXU9D3jdR1mLPW1PwtGuxc9kBYFYXpaZ7CngT+B5NjKn4Vd
hNGIm2ltl3trRPGAmNCRXusG+ZC4ea3coKBqUB0CgYAOrobd9hfPZhAM/Hz9i8Hl
yVjNQmiQ0PWUOWCjx+6SHcDMQ0yUK3fNEowE6ovrGpN1nMWmkcYaJpuhkTTOEZlt
+/N4TbhqHWyPPxbDrilDzOa07mbdyy7M/wb5B6vkKyuZ4ihTBw6Ru5axcJMZGDkV
sjCNKDt85y/NmFJiqColCwKBgQCvERnxpxcEOoyPREUzVNNyHaN/p0+vsqaHdhcC
IiMXY+LThQWoDRykc+737iLAPiZktuHjf7r0Lr798LWzd30zqKH52lEe4366qx1a
ovgkRJEuZQAycj8NN4h3X9VdKOtWJJENV3pMNq0Ku4dcbjc+G6M5Pil9CF8byd5m
R8CZLQKBgQCieaqXoXI3PwuchtOp6rXmeMCmC669ukJsbqnlVQpD5Lr9gbNeyIZT
mjx643a5zAgyQ8u/EH9aXKB/iUUgAbNrY0dx09tlRqg361XSZDjzORIJRYqfaS6R
rX3PFqkkQ8ioCiS6TL2f+wFSCgNIzsZv1B+ScGQYzrvB7n/idVY/4A==
-----END RSA PRIVATE KEY-----

3778
lockbox/frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
{
"name": "vue",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.2.0",
"@noble/secp256k1": "^2.3.0",
"pinia": "^3.0.3",
"socket.io-client": "^4.8.1",
"vue": "^3.5.17"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.11",
"@vitejs/plugin-vue": "^6.0.0",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.11",
"vite": "^7.0.0",
"vite-plugin-vue-devtools": "^7.7.7"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,17 @@
<script setup>
import Home from './views/Home.vue';
</script>
<!-- TODO Create API folder -->
<template>
<div class="flex h-screen">
<Home/>
</div>
</template>
<style scoped>
:global(body) {
background-color: black;
}
</style>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@@ -0,0 +1 @@
@import "tailwindcss";

View File

@@ -0,0 +1,29 @@
<template>
<button
:type="type"
:disabled="disabled"
:class="[
'inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2',
disabled
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
: 'bg-white text-black hover:bg-yellow-400',
className
]"
@click="$emit('click', $event)"
>
<slot></slot>
</button>
</template>
<script setup>
defineProps({
type: {
type: String,
default: 'button'
},
disabled: Boolean,
className: String
})
defineEmits(['click'])
</script>

View File

@@ -0,0 +1,37 @@
<template>
<button
:aria-pressed="modelValue"
role="switch"
:aria-checked="modelValue"
@click="toggle"
:class="[
'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2',
modelValue ? 'bg-yellow-400' : 'bg-gray-300 dark:bg-gray-600',
disabled ? 'opacity-50 cursor-not-allowed' : ''
]"
:disabled="disabled"
>
<span
aria-hidden="true"
:class="[
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
modelValue ? 'translate-x-5' : 'translate-x-0'
]"
></span>
</button>
</template>
<script setup>
const props = defineProps({
modelValue: Boolean,
disabled: Boolean
})
const emit = defineEmits(['update:modelValue'])
const toggle = () => {
if (!props.disabled) {
emit('update:modelValue', !props.modelValue)
}
}
</script>

View File

@@ -0,0 +1,12 @@
<script setup>
defineOptions({ inheritAttrs: false })
</script>
<template>
<div
class="rounded-xl border"
:class="$attrs.class"
>
<slot></slot>
</div>
</template>

View File

@@ -0,0 +1,9 @@
<template>
<div class="p-4 pt-0" :class="$attrs.class">
<slot></slot>
</div>
</template>
<script setup>
defineOptions({ inheritAttrs: false })
</script>

View File

@@ -0,0 +1,12 @@
<template>
<p
class="text-sm text-muted-foreground text-gray-500 dark:text-gray-400"
:class="$attrs.class"
>
<slot></slot>
</p>
</template>
<script setup>
defineOptions({ inheritAttrs: false })
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div class="flex items-center p-4 pt-0" :class="$attrs.class">
<slot></slot>
</div>
</template>
<script setup>
defineOptions({ inheritAttrs: false })
</script>

View File

@@ -0,0 +1,9 @@
<script setup>
defineOptions({ inheritAttrs: false })
</script>
<template>
<div class="flex flex-col space-y-1.5 p-4 mb-3" :class="$attrs.class">
<slot></slot>
</div>
</template>

View File

@@ -0,0 +1,12 @@
<template>
<h3
class="font-semibold text-2xl leading-none tracking-tight"
:class="$attrs.class"
>
<slot></slot>
</h3>
</template>
<script setup>
defineOptions({ inheritAttrs: false })
</script>

View File

@@ -0,0 +1,50 @@
<template>
<div class="relative w-full">
<input
v-bind="$attrs"
:id="id"
:type="showPassword ? 'text' : type"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
:placeholder="placeholder"
:disabled="disabled"
:class="[
'w-full pr-10 rounded-md border border-gray-300 px-3 py-2 text-sm text-black shadow-sm focus:outline-none focus:ring-1 focus:ring-yellow-400 focus:border-yellow-400 disabled:cursor-not-allowed disabled:opacity-50',
error ? 'border-red-500 focus:ring-red-500 focus:border-red-500' : ''
]"
/>
<button
v-if="type === 'password'"
type="button"
@click="showPassword = !showPassword"
class="absolute inset-y-0 right-0 flex items-center px-2 text-gray-500"
tabindex="-1"
>
<EyeIcon v-if="showPassword" class="h-4 w-4"></EyeIcon>
<EyeSlashIcon v-else class="h-4 w-4"></EyeSlashIcon>
</button>
</div>
</template>
<script setup>
import { EyeIcon, EyeSlashIcon } from '@heroicons/vue/24/solid'
import { ref } from 'vue'
defineProps({
modelValue: String,
id: String,
type: {
type: String,
default: 'text'
},
placeholder: String,
disabled: Boolean,
error: Boolean
})
defineEmits(['update:modelValue'])
defineOptions({ inheritAttrs: false })
const showPassword = ref(false)
</script>

View File

@@ -0,0 +1,12 @@
<template>
<label :for="forId" :class="['block text-sm font-medium text-gray-700 dark:text-gray-300', className]">
<slot></slot>
</label>
</template>
<script setup>
defineProps({
forId: String,
className: String
})
</script>

View File

@@ -0,0 +1,125 @@
<script setup>
import { ref } from 'vue';
import { TabPanel} from '@headlessui/vue'
import * as secp from '@noble/secp256k1'
import { KeyIcon, ArrowPathIcon, ClipboardDocumentIcon } from '@heroicons/vue/24/solid'
import Card from '../cards/Card.vue';
import CardHeader from '../cards/CardHeader.vue';
import CardTitle from '../cards/CardTitle.vue';
import CardDescription from '../cards/CardDescription.vue';
import CardContent from '../cards/CardContent.vue';
import Label from '../labels/Label.vue';
import InputText from '../inputs/InputText.vue';
import Button from '../buttons/Button.vue';
import ToogleSwitch from '../buttons/ToogleSwitch.vue';
const privateKeyInput = ref("")
const privateKeyB64 = ref("")
const publicKeyB64 = ref("")
const isTextEnabled = ref(true)
async function generate() {
var array = null
if (isTextEnabled.value){
const encoder = new TextEncoder();
const hashBuffer = await crypto.subtle.digest('SHA-256', encoder.encode(privateKeyInput.value));
array = new Uint8Array(hashBuffer);
privateKeyB64.value = btoa(String.fromCharCode(...array))
}else{
try {
const decoded = atob(privateKeyInput.value)
const bytes = new Uint8Array(decoded.length)
for (let i = 0; i < decoded.length; i++) {
bytes[i] = decoded.charCodeAt(i)
}
if (bytes.length !== 32) {
throw new Error('Tamanho inválido')
}
array = bytes
} catch (e) {
array = new Uint8Array(32)
crypto.getRandomValues(array)
privateKeyInput.value = btoa(String.fromCharCode(...array))
}
}
generatePublicKey(array)
}
function generatePublicKey(privateKeyBytes) {
const publicKeyBytes = secp.getPublicKey(privateKeyBytes)
publicKeyB64.value = btoa(String.fromCharCode(...publicKeyBytes))
}
function onSwitchChanged(newValue) {
privateKeyInput.value = ""
privateKeyB64.value = ""
publicKeyB64.value = ""
}
function copy(text){
navigator.clipboard.writeText(text)
}
</script>
<template>
<TabPanel>
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2 text-yellow-400">
<KeyIcon class="h-5 w-5" />
Create New User
</CardTitle>
<CardDescription class="text-slate-200">
Generate a new ECDSA keypair for user authentication
</CardDescription>
</CardHeader>
<CardContent class=" space-y-2">
<!-- Private key Characteres input -->
<div class="space-y-2">
<div class="flex flex-row gap-4">
<Label>Private Key</Label>
<ToogleSwitch v-model="isTextEnabled" @update:modelValue="onSwitchChanged"/>
<Label>Text Password</Label>
</div>
<div class="flex gap-2">
<InputText id="privateKey" type="password" v-model="privateKeyInput" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
<Button @click="generate()" class="gap-2">
<ArrowPathIcon class="h-4 w-4"/>
<span v-if="!privateKeyInput">Generate</span>
</Button>
<Button v-if="privateKeyInput" @click="copy(privateKeyInput)" class="flex items-center gap-2">
<ClipboardDocumentIcon class="h-4 w-4"/>
</Button>
</div>
</div>
<!-- Public Key -->
<div class="space-y-2">
<Label forId="publicKey" class="text-slate-300">Public Key</Label>
<div class="flex gap-2">
<InputText id="publicKey" type="text" v-model="publicKeyB64" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
<Button @click="copy(publicKeyB64)" class="flex items-center gap-2">
<ClipboardDocumentIcon class="h-4 w-4"/>
Copy
</Button>
</div>
</div>
<!-- Private Key -->
<div class="space-y-2" v-if="isTextEnabled">
<Label forId="privateKeyB64" class="text-slate-300">Private Key</Label>
<div class="flex gap-2">
<InputText id="privateKeyB64" type="password" v-model="privateKeyB64" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
<Button @click="copy(privateKeyB64)" class="flex items-center gap-2">
<ClipboardDocumentIcon class="h-4 w-4"/>
Copy
</Button>
</div>
</div>
</CardContent>
</Card>
</TabPanel>
</template>

View File

@@ -0,0 +1,245 @@
<script setup>
import { TabGroup, TabList, Tab, TabPanels, TabPanel} from '@headlessui/vue'
import { KeyIcon, BoltIcon, ShieldCheckIcon, UserPlusIcon, UsersIcon, ClipboardDocumentListIcon} from '@heroicons/vue/24/solid'
import Card from '../cards/Card.vue';
import CardHeader from '../cards/CardHeader.vue';
import CardTitle from '../cards/CardTitle.vue';
import CardDescription from '../cards/CardDescription.vue';
import CardContent from '../cards/CardContent.vue';
import Label from '../labels/Label.vue';
import InputText from '../inputs/InputText.vue';
import Button from '../buttons/Button.vue';
import CardFooter from '../cards/CardFooter.vue';
import { ref } from 'vue';
import socket from '@/plugins/socketio'
const publicKey = ref("")
const targetDifficulty = ref("4")
const lockboxServiceApiMineUrl = "http://127.0.0.1:5001/mine";
const tasks = ref(null)
// TODO Create api file
function start_mining(){
const data = {"public_key":publicKey.value, "force":targetDifficulty.value};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiMineUrl+"/start", requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
list_tasks()
})
.catch(error => {
console.error
('Error:', error);
});
}
function list_tasks(){
fetch(lockboxServiceApiMineUrl+"/list")
.then(response => {
if (!response.ok){
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
tasks.value = data;
})
.catch(error => {
console.error('Error:', error);
});
}
function pause_task(taskId){
const data = {};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiMineUrl+"/pause/"+taskId, requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
list_tasks()
})
.catch(error => {
console.error
('Error:', error);
});
}
function resume_task(taskId){
const data = {};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiMineUrl+"/resume/"+taskId, requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
list_tasks()
})
.catch(error => {
console.error
('Error:', error);
});
}
function cancel_task(taskId){
const data = {};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiMineUrl+"/cancel/"+taskId, requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
list_tasks()
})
.catch(error => {
console.error
('Error:', error);
});
}
socket.on('taskUpdated', function(msg) {
tasks.value[msg.task_id] = msg.result
});
list_tasks()
</script>
<template>
<TabPanel class=" space-y-5">
<!-- Mining user -->
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<BoltIcon class="h-5 w-5" />
Proof of Work
</CardTitle>
<CardDescription class="text-slate-200">
Generate proof of work for a public key
</CardDescription>
</CardHeader>
<CardContent>
<div class="flex flex-row w-full space-x-5 items-center justify-center text-center">
<div class="flex flex-col space-y-2 w-full">
<Label forId="publicKey" class="text-slate-300">Public Key</Label>
<div class="flex gap-2">
<InputText id="publicKey" v-model="publicKey" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
</div>
</div>
<div class="flex flex-col space-y-2 w-full">
<Label forId="targetDifficulty" class="text-slate-300">Target Difficulty (Leading Zeros)</Label>
<div class="flex gap-2">
<InputText id="targetDifficulty" v-model="targetDifficulty" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
</div>
</div>
</div>
</CardContent>
<CardFooter>
<Button @click="start_mining()" class="flex items-center gap-2 w-full">
Start Mining
</Button>
</CardFooter>
</Card>
<!-- Active jobs -->
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2 text-yellow-400">
Active Jobs
</CardTitle>
<CardDescription class="text-slate-200">
Monitor and control proof of work jobs
</CardDescription>
</CardHeader>
<CardContent>
<div class="overflow-y-auto rounded-md p-4">
<CardContent v-for="([taskId, task]) in Object.entries(tasks)" :key="taskId" class="border border-slate-300 rounded mb-2 pt-2">
<div class="grid md:grid-cols-5 gap-4 items-center">
<div class="flex flex-col">
<p class="text-sm text-gray-300 w-full">Public Key</p>
<p class=" text-gray-300">{{task.public_key}}</p>
</div>
<div class="flex flex-col">
<p class="text-sm text-gray-300 w-full">Difficulty</p>
<p class=" text-gray-300">{{task.best_force}}/{{task.target_force}}</p>
</div>
<div class="flex flex-col">
<p class="text-sm text-gray-300 w-full">Nonce</p>
<p class=" text-gray-300">{{task.best_nonce}}</p>
</div>
<div class="flex flex-col">
<p class="text-sm text-gray-300 w-full">Status</p>
<p :class="{
'text-yellow-400': task.status === 'paused',
'text-blue-400': task.status === 'running',
'text-green-400': task.status === 'completed',
'text-red-400': task.status === 'cancelled',
'text-gray-300': !['paused', 'running', 'completed', 'cancelled'].includes(task.status)
}">
{{ task.status }}
</p>
</div>
<div class="flex flex-row space-x-3 items-center mt-2">
<div v-if="!['completed', 'cancelled', 'error'].includes(task.status)" class="space-x-3">
<!-- TODO Change to Icons -->
<Button v-if="['running'].includes(task.status)" @click="pause_task(taskId)">Pause</Button>
<Button v-if="['paused'].includes(task.status)" @click="resume_task(taskId)">Playy</Button>
<Button @click="cancel_task(taskId)">Cancel</Button>
</div>
</div>
</div>
</CardContent>
</div>
</CardContent>
</Card>
</TabPanel>
</template>

View File

@@ -0,0 +1,126 @@
<script setup>
import { TabGroup, TabList, Tab, TabPanels, TabPanel} from '@headlessui/vue'
import { KeyIcon, BoltIcon, ShieldCheckIcon, UserPlusIcon, UsersIcon, ClipboardDocumentListIcon} from '@heroicons/vue/24/solid'
import Card from '../cards/Card.vue';
import CardHeader from '../cards/CardHeader.vue';
import CardTitle from '../cards/CardTitle.vue';
import CardDescription from '../cards/CardDescription.vue';
import CardContent from '../cards/CardContent.vue';
import { ref } from 'vue';
import Button from '../buttons/Button.vue';
import { useAuthStore } from '@/stores/auth';
import socket from '@/plugins/socketio'
const lockboxServiceApiUrl = "http://127.0.0.1:5001";
const auth = useAuthStore()
const requestedSignatures = ref([])
function listSignatureRequests(user){
const requestOptions = {
method: 'GET',
headers: {'Authorization': 'Bearer ' + auth.tokens[user]}
};
fetch(lockboxServiceApiUrl+"/users/"+user+"/signatures", requestOptions)
.then(response => {
if (!response.ok){
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
requestedSignatures.value = data
})
.catch(error => {
console.error('Error:', error);
});
}
function approveSignature(requestId, user, approved){
const data = {"approved":approved};
const requestOptions = {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Authorization': 'Bearer ' + auth.tokens[user]},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiUrl+"/users/"+user+"/signatures/"+requestId, requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
updateSignatures()
})
.catch(error => {
console.error
('Error:', error);
});
}
socket.on('userAdded', function(msg) {
updateSignatures()
});
socket.on('signatureWaiting', function(msg) {
updateSignatures()
});
function updateSignatures(){
Object.entries(auth.tokens).forEach(([key, token]) => {
listSignatureRequests(key)
})
}
</script>
<template>
<TabPanel>
<!-- Head -->
<Card >
<CardHeader>
<CardTitle class="flex items-center gap-2 text-yellow-400">
<ClipboardDocumentListIcon class="h-5 w-5" />
Pending Signing Requests
</CardTitle>
<CardDescription class="text-slate-200">
Review and approve signing requests from logged-in users
</CardDescription>
<Button @click="updateSignatures()">Refresh</Button>
</CardHeader>
<CardContent>
<CardContent v-for="request in requestedSignatures" class="border border-slate-300 rounded mb-2 pt-2">
<div class="flex flex-col ">
<div class="flex flex-col w-full">
<p class="font-mono text-sm text-gray-300">{{request.requestId}}</p>
<!-- <div class="flex flex-row space-y-1 gap-2">
<p class="font-mono text-sm text-gray-300">{{request.moduleRequesting}}</p>
<p class="font-mono text-sm text-gray-300">{{request.requestedAt}}</p>
</div> -->
<p class="font-mono text-sm text-gray-300">{{request.publicKey}}</p>
<div class="flex items-center gap-4 text-sm text-gray-400">
<span>{{request.data}}</span>
</div>
</div>
<div class="flex flex-col space-x-3 items-center">
<p class="font-mono text-sm text-gray-300">{{request.action}}</p>
<div class="flex flex-row gap-2">
<Button @click="approveSignature(request.requestId, request.user, true)">Approve</Button>
<Button @click="approveSignature(request.requestId, request.user, false)">Reject</Button>
</div>
</div>
</div>
</CardContent>
</CardContent>
</Card>
</TabPanel>
</template>

View File

@@ -0,0 +1,201 @@
<script setup>
import { TabGroup, TabList, Tab, TabPanels, TabPanel} from '@headlessui/vue'
import { KeyIcon, BoltIcon, ShieldCheckIcon, UserPlusIcon, UsersIcon, ClipboardDocumentListIcon} from '@heroicons/vue/24/solid'
import { useAuthStore } from '@/stores/auth';
import Card from '../cards/Card.vue';
import CardHeader from '../cards/CardHeader.vue';
import CardTitle from '../cards/CardTitle.vue';
import CardDescription from '../cards/CardDescription.vue';
import CardContent from '../cards/CardContent.vue';
import Label from '../labels/Label.vue';
import ToogleSwitch from '../buttons/ToogleSwitch.vue';
import { ref } from 'vue';
import InputText from '../inputs/InputText.vue';
import CardFooter from '../cards/CardFooter.vue';
import Button from '../buttons/Button.vue';
const privateKey = ref("")
const publicKey = ref("")
const proofOfWork = ref("")
const credentialPassword = ref("")
const isLoginEnabled = ref(false)
const lockboxServiceApiUrl = "http://127.0.0.1:5001";
const auth = useAuthStore()
const users = ref({})
function onSwitchChanged(newValue) {
// privateKeyInput.value = ""
// privateKeyB64.value = ""
// publicKeyB64.value = ""
}
function set_login_user(userId){
if(!isLoginEnabled.value){
isLoginEnabled.value = true
}
publicKey.value = userId
}
function add_user(){
const data = {password:privateKey.value, data:{proof_of_work:proofOfWork.value}, credentialPassword:credentialPassword.value};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiUrl+"/users", requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
auth.setToken(data.verifying_key, data.token);
})
.catch(error => {
console.error
('Error:', error);
});
}
function login_user(){
const data = {credentialPassword:credentialPassword.value};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiUrl+"/users/"+publicKey.value+"/login", requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
auth.setToken(data.verifying_key, data.token);
})
.catch(error => {
console.error
('Error:', error);
});
}
function logout_user(userId){
delete auth.tokens[userId]
list_users()
}
function list_users(){
fetch(lockboxServiceApiUrl+"/users")
.then(response => {
if (!response.ok){
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
users.value = data;
})
.catch(error => {
console.error('Error:', error);
});
}
list_users()
</script>
<template>
<TabPanel class="flex flex-col space-y-3">
<Card class="">
<CardHeader>
<CardTitle class="flex items-center gap-2 text-yellow-400">
<UserPlusIcon class="h-5 w-5" />
Add User
</CardTitle>
<CardDescription class="text-slate-200">
Add a user with private key or login with public key
</CardDescription>
</CardHeader>
<CardContent class=" space-y-2">
<div class="flex flex-row gap-4">
<Label>Add with Private Key</Label>
<ToogleSwitch v-model="isLoginEnabled" @update:modelValue="onSwitchChanged"/>
<Label>Login with Public Key</Label>
</div>
<div v-if="!isLoginEnabled" class="flex flex-col space-y-2 w-full">
<Label forId="privateKey" class="text-slate-300">Private Key</Label>
<InputText id="privateKey" type="password" v-model="privateKey" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
</div>
<div v-if="!isLoginEnabled" class="flex flex-col space-y-2 w-full">
<Label forId="proofOfWork" class="text-slate-300">Proof of Work</Label>
<InputText id="proofOfWork" v-model="proofOfWork" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
</div>
<div v-if="isLoginEnabled" class="flex flex-col space-y-2 w-full">
<Label forId="publicKey" class="text-slate-300">Public Key</Label>
<InputText id="publicKey" v-model="publicKey" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
</div>
<div class="flex flex-col space-y-2 w-full">
<Label forId="credentialPassword" class="text-slate-300">Credential Password</Label>
<InputText id="credentialPassword" type="password" v-model="credentialPassword" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
</div>
</CardContent>
<CardFooter>
<Button v-if="!isLoginEnabled" @click="add_user()" class="flex items-center gap-2 w-full">
Save
</Button>
<Button v-if="isLoginEnabled" @click="login_user()" class="flex items-center gap-2 w-full">
Login
</Button>
</CardFooter>
</Card>
<Card >
<CardHeader>
<CardTitle class="flex items-center gap-2 text-yellow-400">
<UsersIcon class="h-5 w-5" />
User Management
</CardTitle>
<CardDescription class="text-slate-200">
View and manage your users in the system
</CardDescription>
</CardHeader>
<CardContent>
<CardContent v-for="user in users" class="border border-slate-300 rounded mb-2 pt-2">
<div class="flex flex-col ">
<div class="space-y-1 w-full">
<p class="font-mono text-sm text-gray-300">{{user.id}}</p>
<div class="flex items-center gap-4 text-sm text-gray-400">
<span>Added At: {{user.added_at}}</span>
</div>
</div>
<div class="flex flex-row space-x-3 items-center">
<div v-if="user.id in auth.tokens">
<p class="">Logged In</p>
<Button @click="logout_user(user.id)">Logout</Button>
</div>
<div v-if="!(user.id in auth.tokens)">
<p class="">Logged Out</p>
<Button @click="set_login_user(user.id)">Login</Button>
</div>
</div>
</div>
</CardContent>
</CardContent>
</Card>
</TabPanel>
</template>

View File

@@ -0,0 +1,12 @@
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import socket from './plugins/socketio'
const app = createApp(App)
app.use(createPinia())
app.config.globalProperties.$socket = socket
app.mount('#app')

View File

@@ -0,0 +1,3 @@
import { io } from 'socket.io-client'
const socket = io('https://localhost:5001')
export default socket

View File

@@ -0,0 +1,21 @@
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
tokens: {},
}),
actions: {
setToken(publicKey, token) {
this.tokens[publicKey] = token
localStorage.setItem(publicKey, token)
},
clearToken(publicKey) {
this.tokens[publicKey] = null
localStorage.removeItem(publicKey)
},
loadTokenFromStorage(publicKey) {
const token = localStorage.getItem(publicKey)
if (token) this.tokens[publicKey] = token
}
},
})

View File

@@ -0,0 +1,67 @@
<script setup>
import { TabGroup, TabList, Tab, TabPanels, TabPanel} from '@headlessui/vue'
import { KeyIcon, BoltIcon, ShieldCheckIcon, UserPlusIcon, UsersIcon, ClipboardDocumentListIcon} from '@heroicons/vue/24/solid'
import { ref } from 'vue'
import Card from '@/components/cards/Card.vue'
import CardHeader from '@/components/cards/CardHeader.vue'
import CardTitle from '@/components/cards/CardTitle.vue'
import CardDescription from '@/components/cards/CardDescription.vue'
import CardContent from '@/components/cards/CardContent.vue'
import CardFooter from '@/components/cards/CardFooter.vue'
import TabCreateUser from '@/components/tabs/TabCreateUser.vue'
import TabProofOfWork from '@/components/tabs/TabProofOfWork.vue'
import TabAddUser from '@/components/tabs/TabUsers.vue'
import TabSigningRequests from '@/components/tabs/TabSigningRequests.vue'
const tabItems = [
{label:"Create User", icon:KeyIcon, tabComponent:TabCreateUser},
{label:"Proof of Work", icon:BoltIcon, tabComponent:TabProofOfWork},
{label:"Users", icon:UserPlusIcon, tabComponent:TabAddUser},
{label:"Signing Requests", icon:ClipboardDocumentListIcon, tabComponent:TabSigningRequests},
]
</script>
<template>
<div class="min-h-screen bg-black w-screen text-yellow-400">
<header className="border-b border-yellow-400/20 py-6">
<div className="container mx-auto px-6">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="text-3xl font-black">
<span className="text-yellow-400">LOCK</span>
<span className="text-white">BOX</span>
</div>
<div className="w-1 h-8 bg-yellow-400"></div>
<span className="text-gray-400">Secure ECDSA Keypair Management</span>
</div>
</div>
</div>
</header>
<div className="container mx-auto px-6 py-8">
<TabGroup>
<TabList class="flex flex-wrap gap-2 mb-8 border-b border-yellow-400/20">
<Tab v-for="tab in tabItems" as="template" :key="tab" v-slot="{ selected }">
<button class="flex items-center space-x-2 px-6 py-3 font-semibold transition-all duration-300 border-b-2"
:class="{ 'text-yellow-400 border-yellow-400': selected, 'text-gray-400 border-transparent hover:text-yellow-400 hover:border-yellow-400/50': !selected }">
<component :is="tab.icon" class="h-4 w-4"></component>
<span>{{ tab.label }}</span>
</button>
</Tab>
</TabList>
<TabPanels class="mt-6 space-y-6 px-40" v-for="tab in tabItems" :key="tab.label">
<component :is="tab.tabComponent"></component>
</TabPanels>
</TabGroup>
</div>
</div>
</template>
<style scoped>
/* button {
transition: background 0.2s, color 0.2s;
} */
</style>

View File

@@ -0,0 +1,23 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
tailwindcss(),
],
server: {
port: 3001,
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})

View File

@@ -0,0 +1,52 @@
import os
import webview
import os
import platform
import subprocess
def run_webview():
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain(certfile=cert_path, keyfile=key_path)
url = "https://localhost:5001/"
webview.create_window(
"Unlock Key",
url,
width=800,
height=800,
resizable=True,
confirm_close=True,
text_select=True,
frameless=False,
background_color="#FFFFFF",
)
webview.start()
if __name__ == "__main__":
dir = os.path.dirname(os.path.abspath(__file__))
ca_path = os.path.join(dir, "ca.pem")
ca_key_path = os.path.join(dir, "ca_key.pem")
cert_path = os.path.join(dir, "cert.pem")
key_path = os.path.join(dir, "key.pem")
if not os.path.exists(cert_path) or not os.path.exists(key_path) or not os.path.exists(ca_path) or not os.path.exists(ca_key_path) :
ca, cert, key = generate_ca_and_cert(ca_path, ca_key_path, cert_path, key_path)
system = platform.system()
if system == "Windows":
add_ca_windows(ca)
elif system == "Darwin":
add_ca_macos(ca)
elif system == "Linux":
add_ca_linux(ca)
else:
raise Exception("OS not supported")
print("Cert installed")
else:
print("Cert already exists")
run_webview()

15
lockbox/info.json Normal file
View File

@@ -0,0 +1,15 @@
{
"id": "lockbox",
"version": 0.097,
"modules": [
{
"id": "lockboxClient",
"version": 0
},
{
"id": "miner",
"version": 0
}
],
"frontend": "vue"
}

154
lockbox/lockboxClient.py Normal file
View File

@@ -0,0 +1,154 @@
import requests
import urllib.parse
from threading import Thread
import os
import ssl
import socketio
import logging, traceback
import json
import time
from libs.app.common.paths import ROOT_DIR
from libs.app.common.logging import get_logger
from libs.app.common.network_utils import check_url
from libs.app.common.process import new_python_process
from libs.noSys.noSysModule import NoSysModule
from libs.noSys.users import User
from .lockboxService import UserData
logger = get_logger()
class LockboxClient(NoSysModule):
def __init__(self, nosys_core):
super().__init__(nosys_core)
self.api_host = self.config.get("server").get("host")
self.api_port = self.config.get("server").get("port")
self.service_url = f'{self.api_host}:{self.api_port}'
self.session = requests.Session()
certs_path = os.path.join(ROOT_DIR, "libs", "api", "certs")
ca_path = os.path.join(certs_path, "ca.pem")
self.session.verify = ca_path
session_socketio = requests.Session()
session_socketio.verify = ca_path
self.socketio = socketio.Client(http_session=session_socketio)
self.sio_events()
self.users = {} # UserData
self.user_tokens = {}
self.signature_requests = {} # request_id | callback
def setup(self):
self.nosys_core.modules.pmc = self
def on_nosys_ready(self, event):
logger.debug("LOCKBOX READY")
self.starts_lockbox_service()
self.connect()
# TODO Remove it
# self.add("I0x1Y2FzR2FicmllbFZhekRvc1NhbnRvc0luYWNpbyE=", UserData(proof_of_work="eYnU*@"))
self.users = self.user_list()
for user in self.users:
u = User(user)
self.nosys_core.users.add_user(u)
def starts_lockbox_service(self):
try:
health_check_url = f"{self.service_url}/healthCheck"
if not check_url(health_check_url, timeout=3):
logger.debug(f"Starting lockbox Service in a new process")
process_pid = new_python_process("libs/lockbox/lockboxService.py", args=[self.api_port], new_console=True)
logger.debug(f"Lockbox process PID {process_pid}")
else:
logger.debug(f"Lockbox service already running in {health_check_url}")
except Exception:
logger.exception("ERROR")
def add(self, password, data = UserData()):
add_url = f"{self.service_url}/users"
body = {'password': password, 'data':data.__dict__}
result = self.session.post(add_url, json = body)
content = result.json()
user_token = content["token"]
verifying_key = content["verifying_key"]
# self.users[verifying_key] = data
self.user_tokens[verifying_key] = user_token
return (verifying_key, user_token)
def user_list(self):
users_list_url = f"{self.service_url}/users"
result = self.session.get(users_list_url)
for user in result.json():
print("LOGGED", user["logged"])
if user["logged"]:
verifying_key = user["id"]
self.users[verifying_key] = UserData(id=user["id"],proof_of_work=user["proof_of_work"], time_to_live=user["time_to_live"], added_at=user["added_at"], logged=user["logged"])
return self.users
def set_authorization_header(self, user, headers={}):
if user in self.user_tokens:
headers["Authorization"] = f'Bearer {self.user_tokens[user]}'
return headers
def get(self, user):
# users_data_url = f"{self.service_url}/users/{urllib.parse.quote(user, safe='')}"
# headers = self.set_authorization_header(user)
# result = self.session.get(users_data_url, headers=headers)
# content = result.json()
# data = UserData(proof_of_work=content["proof_of_work"])
data = self.users[user]
return data
def sign(self, data, user, callback, info=None):
sign_url = f"{self.service_url}/users/{urllib.parse.quote(user, safe='')}/signatures"
headers = self.set_authorization_header(user)
body = {'data': data, "info":info}
result = self.session.post(sign_url, json = body, headers=headers)
request_id = result.json()["requestId"]
self.signature_requests[request_id] = callback
return request_id
def connect(self):
while True:
try:
logger.debug(f"Trying to connect to lockbox service {self.service_url}")
self.socketio.connect(self.service_url, wait=True ,wait_timeout=60, transports=["websocket"])
logger.debug(f"Lockbox service connected {self.service_url}")
break
except Exception:
logger.exception(f"Failed to connect to lockbox service {self.service_url} - Retrying in 10 seconds")
time.sleep(10)
# self.socketio.emit('message', {'from': 'client'})
def sio_events(self):
@self.socketio.on('message')
def message(*args, **kwargs):
logger.debug("Message",args, kwargs)
@self.socketio.on("userAdded")
def on_user_added(*args, **kwargs):
user_id = args[0]
logger.debug(f"User added {user_id}")
self.user_list()
if user_id in self.users:
u = User(user_id)
self.nosys_core.users.add_user(u)
@self.socketio.on("signatureWaiting")
def on_signature_waiting(*args, **kwargs):
logger.debug(f"Signature Waiting {args[0]}")
@self.socketio.on("signatureResponse")
def on_signature_response(*args, **kwargs):
logger.debug(f"Signature response {args[0]}")
request_id = args[0]["requestId"]
signature = args[0]["signature"]
if request_id in self.signature_requests:
self.signature_requests[request_id](request_id, signature)
# signature = result.json()["signature"]
# if signature:
# return signature
# else:
# raise Exception("Sign data failed")

168
lockbox/lockboxService.py Normal file
View File

@@ -0,0 +1,168 @@
import os, sys
root_dir = os.path.normpath(__file__.split("libs")[0])
sys.path.append(root_dir)
from threading import Thread
from libs.fspn.utils import sha256_util, aes_util, ecdh_util, ecdsa_util
from libs.fspn.utils.wrapper_util import singleton, threaded
from libs.app.common.logging import get_logger, config_root_logger
from libs.lockbox.utils import save_credential_data, get_credential_data, credential_exists, delete_credential, load_history, save_history, delete_from_history, add_to_history
import argparse
import base64
import subprocess
import secrets
import os
import time, datetime
import webbrowser
config_root_logger()
logger = get_logger("service")
class UserData:
def __init__(self, id=None, proof_of_work=None, time_to_live=43200, added_at=None, logged=False):
self.id = id
self.proof_of_work = proof_of_work
self.added_at = added_at
self.time_to_live = time_to_live
self.logged = logged
class User:
def __init__(self, verifying_key, signing_key, data:UserData=None):
self.verifying_key = verifying_key
self.signing_key = signing_key
self.data = data
thread_ws = Thread(target=self.wait_time_to_live, args=[self.data.time_to_live,])
thread_ws.start()
def sign(self, data):
return base64.b64encode(ecdsa_util.sign_message(data, self.signing_key)).decode()
# TODO Fix it
def wait_time_to_live(self, seconds):
time.sleep(seconds)
self.signing_key = None
user_data = UserData(id=self.data.id)
self.data = user_data
class LockboxService:
def __init__(self):
logger.info("Started")
self.users:dict[str, User] = {}
self.signature_requests = {}
self.mining = {}
self.load_user_history()
from libs.lockbox.lockboxServiceApi import LockboxServiceApi
self.api = LockboxServiceApi(self)
# webbrowser.open("http://localhost:5001", new=0, autoraise=True)
def load_user_history(self):
user_history = load_history()
for user in user_history:
if credential_exists(user):
user_data = UserData(id=user)
self.users[user] = User(user, None, user_data)
else:
delete_from_history(user)
def set_user(self, password:bytes, data:UserData):
verifying_key, signing_key = ecdsa_util.create_keys(password)
vk_b64 = base64.b64encode(verifying_key.to_string('compressed')).decode()
data.id = vk_b64
data.added_at = datetime.datetime.now()
data.logged = True
if vk_b64 not in self.users or self.users[vk_b64].signing_key == None:
self.users[vk_b64] = User(verifying_key, signing_key, data)
return vk_b64
def user_add(self, password_str, password_encode="b64", data=UserData(), credential_password=None):
if(password_encode == "b64"):
password = base64.b64decode(password_str)
else:
raise Exception(f"Password encode {password_encode} not supported")
vk_b64 = self.set_user(password, data)
if credential_password:
if credential_exists(vk_b64):
delete_credential(vk_b64)
credential_data = {"password":password_str,"pof":data.proof_of_work}
save_credential_data(credential_data, vk_b64, credential_password)
logger.debug(f"User added: {vk_b64}")
add_to_history(vk_b64)
return vk_b64
def login_user(self, verifying_key, credential_password):
credential_data = get_credential_data(verifying_key, credential_password)
if credential_data:
password = base64.b64decode(credential_data["password"])
return self.set_user(password, data=UserData(proof_of_work=credential_data["pof"]))
return None
def requested_user_sign(self, verifying_key, data, info):
if verifying_key not in self.users:
return None
request_id = sha256_util.hash_string(data)
self.signature_requests[request_id] = (verifying_key, data, info)
logger.debug(f"Signature {request_id} waiting approvement by {verifying_key}\nInfo:{info}\nData: {data}")
# TODO !!!!!! REMOVE TEST
# self.test(request_id)
return request_id
@threaded
def test(self, request_id):
time.sleep(1)
signature = self.user_sign(request_id, True)
self.api.socketio.emit("signatureResponse",{"requestId":request_id, "signature":signature})
def user_sign(self, request_id, approved):
if request_id not in self.signature_requests:
return None
verifying_key, data, info = self.signature_requests[request_id]
signature = None
if approved:
signature = self.users[verifying_key].sign(data)
logger.debug(f"Request {request_id} signed by {verifying_key} \nSignature: {signature}")
else:
logger.debug(f"Request {request_id} not signed by {verifying_key}")
# TODO Maybe create history
self.signature_requests.pop(request_id)
return signature
def user_list(self):
users = []
for user in self.users.values():
users.append(user.data)
logger.debug(f"Listing users: {users}")
return users
def user_get(self, verifying_key):
data = None
if verifying_key in self.users:
data = self.users[verifying_key].data
logger.debug(f"User {verifying_key} data: {data}")
return data
def user_delete(self, verifying_key):
if verifying_key in self.users:
self.users.pop(verifying_key)
logger.info(f"User {verifying_key} removed")
else:
logger.info(f"User {verifying_key} not exists")
return verifying_key
if __name__ == "__main__":
try:
lockbox = LockboxService()
except Exception as e:
logger.exception("ERROR")

View File

@@ -0,0 +1,170 @@
from flask import Flask, jsonify, request, abort, render_template
from flask_cors import CORS, cross_origin
from flask_socketio import SocketIO
from libs.app.common.logging import get_logger
from libs.app.common.paths import ROOT_DIR
from libs.lockbox.lockboxService import User, UserData
import os
from threading import Thread
import secrets
logger = get_logger("service_api")
FLASK_HOST = "127.0.0.1"
FLASK_PORT = 5001
# TODO Fix JSON to CamelCase not SnakeCase
# TODO Fix websocket events name
class LockboxServiceApi:
def __init__(self, service):
from libs.lockbox.lockboxService import LockboxService
self.service:LockboxService = service
# self.app = Flask(__name__, static_folder=os.path.join(ROOT_DIR, "libs/lockbox/frontend/dist/assets"), template_folder=os.path.join(ROOT_DIR, "libs/lockbox/frontend/dist"))
self.app = Flask(__name__)
self.app.secret_key = secrets.token_urlsafe(16)
self.socketio = SocketIO(self.app, cors_allowed_origins="*", ping_timeout=120, ping_interval=30)
self.valid_tokens = {}
thread_ws = Thread(target=self.run)
thread_ws.start()
def run(self):
self.routes()
self.events()
CORS(self.app)
logger.debug(f"LOCKBOX Flask Running on {FLASK_HOST}:{FLASK_PORT}")
certs_path = os.path.join(ROOT_DIR, "libs", "api", "certs")
cert_path = os.path.join(certs_path, "cert.pem")
key_path = os.path.join(certs_path, "key.pem")
try:
self.socketio.run(self.app, host=FLASK_HOST, port=FLASK_PORT, debug=False, allow_unsafe_werkzeug=True, ssl_context=(cert_path, key_path))
logger.info("DEVERIA ESTAR RODANDO")
except:
logger.exception("DEU ERRO IRMAO")
def events(self):
@self.socketio.on("message")
def on_message(*args, **kwargs):
logger.debug(f'Lockbox message{args}, {kwargs}')
self.socketio.send(f"Message Received")
def generate_token(self, verifying_key):
token = secrets.token_urlsafe(32)
self.valid_tokens[token] = verifying_key
return token
# TODO Separate token for actions. For example approve sign a message
def check_user_token(self, verifying_key):
auth_header = request.headers.get('Authorization', '')
if not auth_header.startswith('Bearer '):
abort(403)
token = auth_header.split(' ')[1]
user = self.valid_tokens.get(token)
if user != verifying_key:
abort(403)
def routes(self):
@self.app.route('/')
@self.app.route('/<path:path>')
def index(path=''):
return render_template('index.html')
@self.app.route("/healthCheck", methods=["GET"])
def health_check():
return jsonify({"status":"running"})
@self.app.route("/users", methods=["GET", "POST"])
def users():
if request.method == "GET":
response = []
for data in self.service.user_list():
if data:
response.append(data.__dict__)
return response
elif request.method == "POST":
# TODO FIX IT!!
content:dict = request.json
password = content["password"]
data = content["data"]
user_data = UserData(proof_of_work=data["proof_of_work"])
credential_password = content.get("credentialPassword")
verifying_key = self.service.user_add(password_str=password, data=user_data, credential_password=credential_password)
if not verifying_key:
return jsonify({"verifying_key":None, "token":None})
token = self.generate_token(verifying_key)
self.socketio.emit("userAdded",f"{verifying_key}")
body = {"verifying_key":verifying_key, "token":token}
return jsonify(body)
@self.app.route("/users/<path:verifying_key>/login", methods=["POST"])
def user_login(verifying_key):
content:dict = request.json
verifying_key = self.service.login_user(verifying_key, content["credentialPassword"])
if not verifying_key:
return jsonify({"verifying_key":None, "token":None})
token = self.generate_token(verifying_key)
self.socketio.emit("userAdded",f"{verifying_key}")
body = {"verifying_key":verifying_key, "token":token}
return jsonify(body)
@self.app.route("/users/<path:verifying_key>", methods=["GET", "POST", "DELETE"])
def user(verifying_key):
self.check_user_token(verifying_key)
if request.method == "GET":
data = self.service.user_get(verifying_key)
if data:
return jsonify(data.__dict__)
else:
return jsonify({})
elif request.method == "POST":
return jsonify({})
elif request.method == "DELETE":
result = self.service.user_delete(verifying_key)
self.socketio.emit("userRemoved",f"{verifying_key}")
return jsonify(result)
@self.app.route("/users/<path:verifying_key>/signatures", methods=["GET", "POST"])
def user_signatures(verifying_key):
if request.method == "GET":
self.check_user_token(verifying_key)
# TODO Create list_signature_requests(verifying_key) in lockbox service
requests = []
for request_id in self.service.signature_requests:
user, data, info = self.service.signature_requests[request_id]
if user == verifying_key:
requests.append({"requestId":request_id, "user":user, "data":data, "info":info})
return requests
elif request.method == "POST":
content = request.json
info = content["info"]
data = content["data"]
request_id = self.service.requested_user_sign(verifying_key, data, info)
if request_id:
self.socketio.emit("signatureWaiting",{"requestId":request_id, "verifyingKey":verifying_key, "info":info})
return jsonify({"requestId":request_id})
@self.app.route("/users/<path:verifying_key>/signatures/<path:request_id>", methods=["GET", "POST"])
def user_signatures_request(verifying_key, request_id):
self.check_user_token(verifying_key)
if request.method == "GET":
# TODO Create get_signature_request(request_id) in lockbox service
user, data, info = self.service.signature_requests[request_id]
return jsonify({"data":data, "info":info})
elif request.method == "POST":
content = request.json
approved = content["approved"]
signature = self.service.user_sign(request_id, approved)
self.socketio.emit("signatureResponse",{"requestId":request_id, "signature":signature})
return jsonify({"signature":signature})

161
lockbox/miner.py Normal file
View File

@@ -0,0 +1,161 @@
import time
import copy
import threading
import uuid
import string, random, hashlib, datetime
from multiprocessing import Process, Manager, Event
from libs.noSys.noSysModule import NoSysModule
from .minerApiBlueprint import Blueprint
from .minerSocketio import HandlerSocketio
class Miner(NoSysModule):
def __init__(self, nosys_core):
super().__init__(nosys_core)
self.manager = Manager()
self.tasks = {}
def setup(self):
self.nosys_core.modules.api.register_blueprint(Blueprint(self).blueprint)
self.socketio = HandlerSocketio(self)
self.nosys_core.modules.api.register_socketio(self.socketio)
def monitor_task_result(self, task_id, result):
last_state = {}
while True:
try:
current_state = dict(result)
except Exception:
break
if current_state != last_state:
self.socketio.emit("taskUpdated", {
"task_id": task_id,
"result": current_state
})
last_state = copy.deepcopy(current_state)
if current_state.get("status") in ("completed", "cancelled", "error"):
break
time.sleep(1)
def start_mining(self, force, data, nonce_length):
task_id = str(uuid.uuid4())
result = self.manager.dict({
"status": "running",
"target_force": force,
"data": data,
"nonce": None,
"hash": None,
"best_nonce": None,
"best_hash": None,
"best_force": 0,
"attempts": 0,
"duration": 0,
})
stop_event = Event()
pause_event = Event()
proc = Process(target=self.mine_worker, args=(data, force, nonce_length, result, stop_event, pause_event))
proc.start()
monitor_thread = threading.Thread(
target=self.monitor_task_result,
args=(task_id, result),
daemon=True
)
monitor_thread.start()
self.tasks[task_id] = {
"process": proc,
"result": result,
"stop_event": stop_event,
"pause_event": pause_event,
}
return task_id
def pause_task(self, task_id):
task = self.tasks.get(task_id)
if not task:
return False
task["pause_event"].set()
task["result"]["status"] = "paused"
return True
def resume_task(self, task_id):
task = self.tasks.get(task_id)
if not task:
return False
task["pause_event"].clear()
task["result"]["status"] = "running"
return True
def cancel_task(self, task_id):
task = self.tasks.get(task_id)
if not task:
return False
task["stop_event"].set()
task["process"].terminate()
task["process"].join()
task["result"]["status"] = "cancelled"
return True
@staticmethod
def mine_worker(public_key, force, nonce_length, result_dict, stop_event, pause_event):
def hash_string(text):
return hashlib.sha256(text.encode('utf-8')).hexdigest()
def random_nonce(characters, length):
return ''.join(random.choices(characters, k=length))
def count_leading_zeros(hex_hash):
return len(hex_hash) - len(hex_hash.lstrip("0"))
characters = string.ascii_letters + string.digits + '[@_!#$%^&*()<>?/\\|}{~:]'
attempts = 0
best_force = -1
start_time = datetime.datetime.now()
while not stop_event.is_set():
if pause_event.is_set():
pause_event.wait(1)
continue
nonce = random_nonce(characters, nonce_length)
hash_result = hash_string(public_key + nonce)
attempts += 1
leading_zeros = count_leading_zeros(hash_result)
if leading_zeros > best_force:
best_force = leading_zeros
result_dict.update({
"best_nonce": nonce,
"best_hash": hash_result,
"best_force": best_force,
"attempts": attempts,
})
if leading_zeros >= force:
duration = (datetime.datetime.now() - start_time).total_seconds()
result_dict.update({
"status": "completed",
"nonce": nonce,
"hash": hash_result,
"attempts": attempts,
"duration": duration,
"best_force": leading_zeros,
"best_nonce": nonce,
"best_hash": hash_result,
})
stop_event.set()
break
if stop_event.is_set() and result_dict.get("status") not in ("completed", "cancelled"):
result_dict.update({
"status": "cancelled",
"duration": (datetime.datetime.now() - start_time).total_seconds(),
})

View File

@@ -0,0 +1,75 @@
from flask import jsonify, request
from flask_socketio import SocketIO
import threading
from multiprocessing import Process, Manager, Event
import uuid
import copy
import time
from libs.api.apiBlueprint import ApiBlueprint
class Blueprint(ApiBlueprint):
def __init__(self, nosys_module):
super().__init__(nosys_module)
from .miner import Miner
self.module:Miner = nosys_module
def routes(self):
@self.blueprint.route("/start", methods=["POST"])
def start_mining():
data = request.json
public_key = data.get("public_key")
force = int(data.get("force", 5))
nonce_length = data.get("nonce_length", 6)
task_id = self.module.start_mining(force, public_key, nonce_length)
return jsonify({"task_id": task_id})
@self.blueprint.route("/status/<task_id>")
def get_status(task_id):
task = self.module.tasks.get(task_id)
if not task:
return jsonify({"error": "Task not found"}), 404
return jsonify(dict(task["result"]))
@self.blueprint.route("/pause/<task_id>", methods=["POST"])
def pause_mining(task_id):
result = self.module.pause_task(task_id)
if not result:
return jsonify({"error": "Task not found"}), 404
return jsonify({"message": "Mining paused"})
@self.blueprint.route("/resume/<task_id>", methods=["POST"])
def resume_mining(task_id):
result = self.module.resume_task(task_id)
if not result:
return jsonify({"error": "Task not found"}), 404
return jsonify({"message": "Mining resumed"})
@self.blueprint.route("/cancel/<task_id>", methods=["POST"])
def cancel_mining(task_id):
result = self.module.cancel_task(task_id)
if not result:
return jsonify({"error": "Task not found"}), 404
return jsonify({"message": "Mining cancelled"})
@self.blueprint.route('/list', methods=['GET'])
def list_all_tasks():
tasks_info = {}
for task_id, task_data in self.module.tasks.items():
result = task_data["result"]
tasks_info[task_id] = {
"status": result.get("status"),
"attempts": result.get("attempts"),
"data": result.get("data"),
"target_force": result.get("target_force"),
"best_force": result.get("best_force"),
"duration": result.get("duration"),
"nonce": result.get("nonce"),
"best_nonce": result.get("best_nonce"),
"hash": result.get("hash"),
"best_hash": result.get("best_hash")
}
return jsonify(tasks_info)

26
lockbox/minerSocketio.py Normal file
View File

@@ -0,0 +1,26 @@
from flask import Blueprint, make_response, request, jsonify, session, request
from flask_socketio import SocketIO, emit, join_room, leave_room
from libs.api.eventsSocketio import EventsSocketio
import os, json
import logging
class HandlerSocketio(EventsSocketio):
def events(self):
@self.socketio.on("message", namespace=self.namespace)
def on_message(*args, **kwargs):
print('P2Post Message',args, kwargs)
self.socketio.send(f"Message from P2Post {args[0]}", namespace=self.namespace)
@self.socketio.on("ola", namespace=self.namespace)
def on_test(*args, **kwargs):
print('P2Post ola',args, kwargs)
self.socketio.emit("ola",f"Ola from P2Post {args[0]}", namespace=self.namespace)
def send_test(self, message):
self.socketio.emit("test",message, namespace=self.namespace)
def emit_new_user(self, user):
self.socketio.emit("newUser", user, namespace=self.namespace)

2
lockbox/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
keyring
pywebview

3
lockbox/userHistory.json Normal file
View File

@@ -0,0 +1,3 @@
[
"A4DZSk+TlR+4w39MbiIAQbti+N0H1QlJEhRH2DI6Iubj"
]

89
lockbox/utils.py Normal file
View File

@@ -0,0 +1,89 @@
import os, sys
import keyring
import json
import base64
from libs.fspn.utils import sha256_util, aes_util, ecdh_util, ecdsa_util
from libs.app.common.paths import ROOT_DIR
def encrypt_secret(secret: dict, user_password: str) -> str:
secret = json.dumps(secret)
key = ecdh_util.generate_derived_key(user_password.encode())
nonce, ciphertext, mac = aes_util.encrypt(secret.encode(), key)
payload = {
"nonce": base64.b64encode(nonce).decode(),
"ciphertext": base64.b64encode(ciphertext).decode(),
"mac": base64.b64encode(mac).decode()
}
return json.dumps(payload)
def decrypt_secret(payload_json: str, user_password: str) -> str | None:
payload = json.loads(payload_json)
nonce = base64.b64decode(payload["nonce"])
ciphertext = base64.b64decode(payload["ciphertext"])
mac = base64.b64decode(payload["mac"])
key = ecdh_util.generate_derived_key(user_password.encode())
plaintext = aes_util.decrypt_and_verify(nonce, ciphertext, mac, key)
return json.loads(plaintext.decode())
SERVICE_NAME = "NoSys"
def save_credential_data(data, verifying_key_b64, user_password):
credential_name = get_credential_name(verifying_key_b64)
payload = encrypt_secret(data, user_password)
keyring.set_password(credential_name, verifying_key_b64, payload)
def delete_credential(verifying_key_b64):
credential_name = get_credential_name(verifying_key_b64)
keyring.delete_password(credential_name, verifying_key_b64)
def get_credential_data(verifying_key_b64, user_password):
credential_name = get_credential_name(verifying_key_b64)
payload = keyring.get_password(credential_name, verifying_key_b64)
if payload:
data = decrypt_secret(payload, user_password)
return data
return None
def credential_exists(verifying_key_b64):
credential_name = get_credential_name(verifying_key_b64)
payload = keyring.get_password(credential_name, verifying_key_b64)
if payload:
return True
return False
def get_credential_name(verifying_key_b64):
return f"{SERVICE_NAME}|{verifying_key_b64}"
import json
import os
HISTORY_FILE = os.path.join(ROOT_DIR, "libs/lockbox", "userHistory.json")
def load_history():
if os.path.exists(HISTORY_FILE):
with open(HISTORY_FILE, "r") as f:
return json.load(f)
return []
def save_history(history):
with open(HISTORY_FILE, "w") as f:
json.dump(history, f, indent=2)
def add_to_history(item):
history = load_history()
if item not in history:
history.append(item)
save_history(history)
def delete_from_history(item):
history = load_history()
if item in history:
history.remove(item)
save_history(history)
return True
return False

View File

View File

View File

@@ -0,0 +1,7 @@
export default function registerSocketEvents(socket) {
socket.on('connect', (data) => {
console.log('Connected LockboxClient')
})
}

View File

@@ -0,0 +1,125 @@
<script setup>
import { ref } from 'vue';
import { TabPanel} from '@headlessui/vue'
import * as secp from '@noble/secp256k1'
import { KeyIcon, ArrowPathIcon, ClipboardDocumentIcon } from '@heroicons/vue/24/solid'
import Card from '@/components/cards/Card.vue';
import CardHeader from '@/components/cards/CardHeader.vue';
import CardTitle from '@/components/cards/CardTitle.vue';
import CardDescription from '@/components/cards/CardDescription.vue';
import CardContent from '@/components/cards/CardContent.vue';
import Label from '@/components/labels/Label.vue';
import InputText from '@/components/inputs/InputText.vue';
import Button from '@/components/buttons/Button.vue';
import ToogleSwitch from '@/components/buttons/ToogleSwitch.vue';
const privateKeyInput = ref("")
const privateKeyB64 = ref("")
const publicKeyB64 = ref("")
const isTextEnabled = ref(true)
async function generate() {
var array = null
if (isTextEnabled.value){
const encoder = new TextEncoder();
const hashBuffer = await crypto.subtle.digest('SHA-256', encoder.encode(privateKeyInput.value));
array = new Uint8Array(hashBuffer);
privateKeyB64.value = btoa(String.fromCharCode(...array))
}else{
try {
const decoded = atob(privateKeyInput.value)
const bytes = new Uint8Array(decoded.length)
for (let i = 0; i < decoded.length; i++) {
bytes[i] = decoded.charCodeAt(i)
}
if (bytes.length !== 32) {
throw new Error('Tamanho inválido')
}
array = bytes
} catch (e) {
array = new Uint8Array(32)
crypto.getRandomValues(array)
privateKeyInput.value = btoa(String.fromCharCode(...array))
}
}
generatePublicKey(array)
}
function generatePublicKey(privateKeyBytes) {
const publicKeyBytes = secp.getPublicKey(privateKeyBytes)
publicKeyB64.value = btoa(String.fromCharCode(...publicKeyBytes))
}
function onSwitchChanged(newValue) {
privateKeyInput.value = ""
privateKeyB64.value = ""
publicKeyB64.value = ""
}
function copy(text){
navigator.clipboard.writeText(text)
}
</script>
<template>
<TabPanel>
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2 text-yellow-400">
<KeyIcon class="h-5 w-5" />
Create New User
</CardTitle>
<CardDescription class="text-slate-200">
Generate a new ECDSA keypair for user authentication
</CardDescription>
</CardHeader>
<CardContent class=" space-y-2">
<!-- Private key Characteres input -->
<div class="space-y-2">
<div class="flex flex-row gap-4">
<Label>Private Key</Label>
<ToogleSwitch v-model="isTextEnabled" @update:modelValue="onSwitchChanged"/>
<Label>Text Password</Label>
</div>
<div class="flex gap-2">
<InputText id="privateKey" type="password" v-model="privateKeyInput" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
<Button @click="generate()" class="gap-2">
<ArrowPathIcon class="h-4 w-4"/>
<span v-if="!privateKeyInput">Generate</span>
</Button>
<Button v-if="privateKeyInput" @click="copy(privateKeyInput)" class="flex items-center gap-2">
<ClipboardDocumentIcon class="h-4 w-4"/>
</Button>
</div>
</div>
<!-- Public Key -->
<div class="space-y-2">
<Label forId="publicKey" class="text-slate-300">Public Key</Label>
<div class="flex gap-2">
<InputText id="publicKey" type="text" v-model="publicKeyB64" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
<Button @click="copy(publicKeyB64)" class="flex items-center gap-2">
<ClipboardDocumentIcon class="h-4 w-4"/>
Copy
</Button>
</div>
</div>
<!-- Private Key -->
<div class="space-y-2" v-if="isTextEnabled">
<Label forId="privateKeyB64" class="text-slate-300">Private Key</Label>
<div class="flex gap-2">
<InputText id="privateKeyB64" type="password" v-model="privateKeyB64" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
<Button @click="copy(privateKeyB64)" class="flex items-center gap-2">
<ClipboardDocumentIcon class="h-4 w-4"/>
Copy
</Button>
</div>
</div>
</CardContent>
</Card>
</TabPanel>
</template>

View File

@@ -0,0 +1,259 @@
<script setup>
import { TabPanel} from '@headlessui/vue'
import { KeyIcon, BoltIcon, ShieldCheckIcon, UserPlusIcon, UsersIcon, ClipboardDocumentListIcon} from '@heroicons/vue/24/solid'
import Card from '@/components/cards/Card.vue';
import CardHeader from '@/components/cards/CardHeader.vue';
import CardTitle from '@/components/cards/CardTitle.vue';
import CardDescription from '@/components/cards/CardDescription.vue';
import CardContent from '@/components/cards/CardContent.vue';
import Label from '@/components/labels/Label.vue';
import InputText from '@/components/inputs/InputText.vue';
import Button from '@/components/buttons/Button.vue';
import CardFooter from '@/components/cards/CardFooter.vue';
import { ref } from 'vue';
import { getSocket } from '@/plugins/socketioManager'
import { onMounted, onUnmounted, onActivated, onDeactivated } from 'vue'
var socket = null
const publicKey = ref("")
const targetDifficulty = ref("4")
const lockboxServiceApiMineUrl = "/api/lockbox";
const tasks = ref(null)
// TODO Create api file
function start_mining(){
const data = {"public_key":publicKey.value, "force":targetDifficulty.value};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiMineUrl+"/start", requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
})
.catch(error => {
console.error
('Error:', error);
});
}
function list_tasks(){
fetch(lockboxServiceApiMineUrl+"/list")
.then(response => {
if (!response.ok){
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
tasks.value = data;
})
.catch(error => {
console.error('Error:', error);
});
}
function pause_task(taskId){
const data = {};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiMineUrl+"/pause/"+taskId, requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
})
.catch(error => {
console.error
('Error:', error);
});
}
function resume_task(taskId){
const data = {};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiMineUrl+"/resume/"+taskId, requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
})
.catch(error => {
console.error
('Error:', error);
});
}
function cancel_task(taskId){
const data = {};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiMineUrl+"/cancel/"+taskId, requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
})
.catch(error => {
console.error
('Error:', error);
});
}
function onTaskUpdated(data){
console.log(data)
tasks.value[data.task_id] = data.result
}
onActivated(async () => {
list_tasks();
socket = await getSocket("lockbox_miner")
socket.on('taskUpdated', onTaskUpdated);
});
onDeactivated(()=>{
socket.off('taskUpdated', onTaskUpdated);
});
</script>
<template>
<TabPanel class=" space-y-5">
<!-- Mining user -->
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<BoltIcon class="h-5 w-5" />
Proof of Work
</CardTitle>
<CardDescription class="text-slate-200">
Generate proof of work for a data
</CardDescription>
</CardHeader>
<CardContent>
<div class="flex flex-row w-full space-x-5 items-center justify-center text-center">
<div class="flex flex-col space-y-2 w-full">
<Label forId="publicKey" class="text-slate-300">Data</Label>
<div class="flex gap-2">
<InputText id="publicKey" v-model="publicKey" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
</div>
</div>
<div class="flex flex-col space-y-2 w-full">
<Label forId="targetDifficulty" class="text-slate-300">Target Difficulty (Leading Zeros)</Label>
<div class="flex gap-2">
<InputText id="targetDifficulty" v-model="targetDifficulty" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
</div>
</div>
</div>
</CardContent>
<CardFooter>
<Button @click="start_mining()" class="flex items-center gap-2 w-full">
Start Mining
</Button>
</CardFooter>
</Card>
<!-- Active jobs -->
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2 text-yellow-400">
Active Jobs
</CardTitle>
<CardDescription class="text-slate-200">
Monitor and control proof of work jobs
</CardDescription>
</CardHeader>
<CardContent>
<div class="overflow-y-auto rounded-md p-4">
<CardContent v-for="([taskId, task]) in Object.entries(tasks)" :key="taskId" class="border border-slate-300 rounded mb-2 pt-2">
<div class="grid md:grid-cols-5 gap-4 items-center">
<div class="flex flex-col">
<p class="text-sm text-gray-300 w-full">Data</p>
<p class=" text-gray-300">{{task.data}}</p>
</div>
<div class="flex flex-col">
<p class="text-sm text-gray-300 w-full">Difficulty</p>
<p class=" text-gray-300">{{task.best_force}}/{{task.target_force}}</p>
</div>
<div class="flex flex-col">
<p class="text-sm text-gray-300 w-full">Nonce</p>
<p class=" text-gray-300">{{task.best_nonce}}</p>
</div>
<div class="flex flex-col">
<p class="text-sm text-gray-300 w-full">Status</p>
<p :class="{
'text-yellow-400': task.status === 'paused',
'text-blue-400': task.status === 'running',
'text-green-400': task.status === 'completed',
'text-red-400': task.status === 'cancelled',
'text-gray-300': !['paused', 'running', 'completed', 'cancelled'].includes(task.status)
}">
{{ task.status }}
</p>
</div>
<div class="flex flex-row space-x-3 items-center mt-2">
<div v-if="!['completed', 'cancelled', 'error'].includes(task.status)" class="space-x-3">
<!-- TODO Change to Icons -->
<Button v-if="['running'].includes(task.status)" @click="pause_task(taskId)">Pause</Button>
<Button v-if="['paused'].includes(task.status)" @click="resume_task(taskId)">Play</Button>
<Button @click="cancel_task(taskId)">Cancel</Button>
</div>
</div>
</div>
</CardContent>
</div>
</CardContent>
</Card>
</TabPanel>
</template>

View File

@@ -0,0 +1,136 @@
<script setup>
import { TabGroup, TabList, Tab, TabPanels, TabPanel} from '@headlessui/vue'
import { KeyIcon, BoltIcon, ShieldCheckIcon, UserPlusIcon, UsersIcon, ClipboardDocumentListIcon} from '@heroicons/vue/24/solid'
import Card from '@/components/cards/Card.vue';
import CardHeader from '@/components/cards/CardHeader.vue';
import CardTitle from '@/components/cards/CardTitle.vue';
import CardDescription from '@/components/cards/CardDescription.vue';
import CardContent from '@/components/cards/CardContent.vue';
import { onActivated, onDeactivated, ref } from 'vue';
import Button from '@/components/buttons/Button.vue';
import { useAuthStore } from '../../stores/auth';
import { io } from 'socket.io-client';
var socket = null
const lockboxServiceApiUrl = "https://127.0.0.1:5001";
const auth = useAuthStore()
const requestedSignatures = ref([])
async function listSignatureRequests(user){
const requestOptions = {
method: 'GET',
headers: {'Authorization': 'Bearer ' + auth.tokens[user]}
};
fetch(lockboxServiceApiUrl+"/users/"+user+"/signatures", requestOptions)
.then(response => {
if (!response.ok){
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
requestedSignatures.value = data
})
.catch(error => {
console.error('Error:', error);
});
}
function approveSignature(requestId, user, approved){
const data = {"approved":approved};
const requestOptions = {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Authorization': 'Bearer ' + auth.tokens[user]},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiUrl+"/users/"+user+"/signatures/"+requestId, requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
updateSignatures()
})
.catch(error => {
console.error
('Error:', error);
});
}
function onUserAdded(data){
console.log(data)
updateSignatures()
}
function onSignatureWaiting(data){
console.log(data)
updateSignatures()
}
onActivated(async () => {
// const socket = await getSocket("lockbox_lockboxClient")
socket = io(lockboxServiceApiUrl)
socket.on('userAdded', onUserAdded);
socket.on('signatureWaiting', onSignatureWaiting);
updateSignatures();
});
onDeactivated(()=>{
socket.off('userAdded', onUserAdded);
socket.off('signatureWaiting', onSignatureWaiting);
});
function updateSignatures(){
Object.entries(auth.tokens).forEach(([key, token]) => {
listSignatureRequests(key)
})
}
</script>
<template>
<TabPanel>
<!-- Head -->
<Card >
<CardHeader>
<CardTitle class="flex items-center gap-2 text-yellow-400">
<ClipboardDocumentListIcon class="h-5 w-5" />
Pending Signing Requests
</CardTitle>
<CardDescription class="text-slate-200">
Review and approve signing requests from logged-in users
</CardDescription>
</CardHeader>
<CardContent>
<CardContent v-for="request in requestedSignatures" class="border border-slate-300 rounded mb-2 pt-2">
<div class="flex flex-row space-x-5">
<div class="flex flex-col w-full">
<p class="font-mono text-sm text-gray-300">{{request.publicKey}}</p>
<p class="font-mono text-sm text-gray-300">{{request.requestId}}</p>
<p class="font-mono text-sm text-gray-300">{{request.info}}</p>
<div class="flex items-center gap-4 text-sm text-gray-400">
<span>{{request.data}}</span>
</div>
</div>
<div class="flex flex-col space-x-3 items-center">
<p class="font-mono text-sm text-gray-300">{{request.action}}</p>
<div class="flex flex-row gap-2">
<Button class="bg-green-600" @click="approveSignature(request.requestId, request.user, true)">Approve</Button>
<Button class="bg-red-600" @click="approveSignature(request.requestId, request.user, false)">Reject</Button>
</div>
</div>
</div>
</CardContent>
</CardContent>
</Card>
</TabPanel>
</template>

View File

@@ -0,0 +1,219 @@
<script setup>
import { TabGroup, TabList, Tab, TabPanels, TabPanel} from '@headlessui/vue'
import { KeyIcon, BoltIcon, ShieldCheckIcon, UserPlusIcon, UsersIcon, ClipboardDocumentListIcon} from '@heroicons/vue/24/solid'
import { useAuthStore } from '../../stores/auth';
import Card from '@/components/cards/Card.vue';
import CardHeader from '@/components/cards/CardHeader.vue';
import CardTitle from '@/components/cards/CardTitle.vue';
import CardDescription from '@/components/cards/CardDescription.vue';
import CardContent from '@/components/cards/CardContent.vue';
import Label from '@/components/labels/Label.vue';
import ToogleSwitch from '@/components/buttons/ToogleSwitch.vue';
import { onActivated, onDeactivated, ref } from 'vue';
import InputText from '@/components/inputs/InputText.vue';
import CardFooter from '@/components/cards/CardFooter.vue';
import Button from '@/components/buttons/Button.vue';
import { getSocket } from '@/plugins/socketioManager'
import { io } from 'socket.io-client';
var socket = null
const privateKey = ref("")
const publicKey = ref("")
const proofOfWork = ref("")
const credentialPassword = ref("")
const isLoginEnabled = ref(false)
const lockboxServiceApiUrl = "https://127.0.0.1:5001";
const auth = useAuthStore()
const users = ref({})
function onSwitchChanged(newValue) {
// privateKeyInput.value = ""
// privateKeyB64.value = ""
// publicKeyB64.value = ""
}
function set_login_user(userId){
if(!isLoginEnabled.value){
isLoginEnabled.value = true
}
publicKey.value = userId
}
function add_user(){
const data = {password:privateKey.value, data:{proof_of_work:proofOfWork.value}, credentialPassword:credentialPassword.value};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiUrl+"/users", requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
auth.setToken(data.verifying_key, data.token);
})
.catch(error => {
console.error
('Error:', error);
});
}
function login_user(){
const data = {credentialPassword:credentialPassword.value};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(lockboxServiceApiUrl+"/users/"+publicKey.value+"/login", requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
auth.setToken(data.verifying_key, data.token);
})
.catch(error => {
console.error
('Error:', error);
});
}
function logout_user(userId){
delete auth.tokens[userId]
list_users()
}
function list_users(){
fetch(lockboxServiceApiUrl+"/users")
.then(response => {
if (!response.ok){
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
users.value = data;
})
.catch(error => {
console.error('Error:', error);
});
}
function onUserAdded(data){
console.log(data)
list_users();
}
onActivated(async () => {
list_users();
// const socket = await getSocket("lockbox_lockboxClient")
socket = io(lockboxServiceApiUrl)
socket.on('userAdded', onUserAdded);
});
onDeactivated(()=>{
socket.off('userAdded', onUserAdded);
});
</script>
<template>
<TabPanel class="flex flex-col space-y-3">
<Card class="">
<CardHeader>
<CardTitle class="flex items-center gap-2 text-yellow-400">
<UserPlusIcon class="h-5 w-5" />
Add User
</CardTitle>
<CardDescription class="text-slate-200">
Add a user with private key or login with public key
</CardDescription>
</CardHeader>
<CardContent class=" space-y-2">
<div class="flex flex-row gap-4">
<Label>Add with Private Key</Label>
<ToogleSwitch v-model="isLoginEnabled" @update:modelValue="onSwitchChanged"/>
<Label>Login with Public Key</Label>
</div>
<div v-if="!isLoginEnabled" class="flex flex-col space-y-2 w-full">
<Label forId="privateKey" class="text-slate-300">Private Key</Label>
<InputText id="privateKey" type="password" v-model="privateKey" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
</div>
<div v-if="!isLoginEnabled" class="flex flex-col space-y-2 w-full">
<Label forId="proofOfWork" class="text-slate-300">Proof of Work</Label>
<InputText id="proofOfWork" v-model="proofOfWork" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
</div>
<div v-if="isLoginEnabled" class="flex flex-col space-y-2 w-full">
<Label forId="publicKey" class="text-slate-300">Public Key</Label>
<InputText id="publicKey" v-model="publicKey" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
</div>
<div class="flex flex-col space-y-2 w-full">
<Label forId="credentialPassword" class="text-slate-300">Credential Password</Label>
<InputText id="credentialPassword" type="password" v-model="credentialPassword" placeholder="" class="border-gray-700 bg-slate-300 w-full"/>
</div>
</CardContent>
<CardFooter>
<Button v-if="!isLoginEnabled" @click="add_user()" class="flex items-center gap-2 w-full">
Save
</Button>
<Button v-if="isLoginEnabled" @click="login_user()" class="flex items-center gap-2 w-full">
Login
</Button>
</CardFooter>
</Card>
<Card >
<CardHeader>
<CardTitle class="flex items-center gap-2 text-yellow-400">
<UsersIcon class="h-5 w-5" />
User Management
</CardTitle>
<CardDescription class="text-slate-200">
View and manage your users in the system
</CardDescription>
</CardHeader>
<div class="px-4">
<CardContent v-for="user in users" class="border border-yellow-400/20 rounded mb-2 pt-2">
<div class="flex flex-row items-start gap-x-5">
<div class="space-y-1">
<p class="font-mono text-sm text-gray-300">{{user.id}}</p>
<div class="flex items-center gap-4 text-sm text-gray-400">
<span>Added At: {{user.added_at}}</span>
</div>
</div>
<div class="flex flex-row space-x-3 items-center">
<!-- TODO Create field logged:bool -->
<div v-if="user.id in auth.tokens">
<Button class="bg-red-600" @click="logout_user(user.id)">Logout</Button>
</div>
<div v-if="!(user.id in auth.tokens)">
<Button class="bg-green-600" @click="set_login_user(user.id)">Login</Button>
</div>
</div>
</div>
</CardContent>
</div>
</Card>
</TabPanel>
</template>

View File

@@ -0,0 +1 @@
@noble/secp256k1

7
lockbox/vue/router.js Normal file
View File

@@ -0,0 +1,7 @@
import HomeView from "./views/HomeView.vue";
const routes = [
{path: '/', name:'lockbox', component: HomeView},
]
export {routes};

View File

@@ -0,0 +1,21 @@
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
tokens: {},
}),
actions: {
setToken(publicKey, token) {
this.tokens[publicKey] = token
localStorage.setItem(publicKey, token)
},
clearToken(publicKey) {
this.tokens[publicKey] = null
localStorage.removeItem(publicKey)
},
loadTokenFromStorage(publicKey) {
const token = localStorage.getItem(publicKey)
if (token) this.tokens[publicKey] = token
}
},
})

View File

@@ -0,0 +1,59 @@
<script setup>
import { TabGroup, TabList, Tab, TabPanels, TabPanel} from '@headlessui/vue'
import { KeyIcon, BoltIcon, ShieldCheckIcon, UserPlusIcon, UsersIcon, ClipboardDocumentListIcon} from '@heroicons/vue/24/solid'
import TabCreateUser from '../components/tabs/TabCreateUser.vue'
import TabProofOfWork from '../components/tabs/TabProofOfWork.vue'
import TabAddUser from '../components/tabs/TabUsers.vue'
import TabSigningRequests from '../components/tabs/TabSigningRequests.vue'
const tabItems = [
{label:"Create User", icon:KeyIcon, tabComponent:TabCreateUser},
{label:"Proof of Work", icon:BoltIcon, tabComponent:TabProofOfWork},
{label:"Users", icon:UserPlusIcon, tabComponent:TabAddUser},
{label:"Signing Requests", icon:ClipboardDocumentListIcon, tabComponent:TabSigningRequests},
]
</script>
<template>
<div class="min-h-screen bg-black w-screen text-yellow-400">
<header className="border-b border-yellow-400/20 py-6">
<div className="container mx-auto px-6">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="text-3xl font-black">
<span className="text-yellow-400">LOCK</span>
<span className="text-white">BOX</span>
</div>
<div className="w-1 h-8 bg-yellow-400"></div>
<span className="text-gray-400">Secure ECDSA Keypair Management</span>
</div>
</div>
</div>
</header>
<div className="container mx-auto px-6">
<TabGroup>
<TabList class="flex flex-wrap gap-2 mb-8 border-b border-yellow-400/20">
<Tab v-for="tab in tabItems" as="template" :key="tab" v-slot="{ selected }">
<button class="flex items-center space-x-2 px-6 py-3 font-semibold transition-all duration-300 border-b-2"
:class="{ 'text-yellow-400 border-yellow-400': selected, 'text-gray-400 border-transparent hover:text-yellow-400 hover:border-yellow-400/50': !selected }">
<component :is="tab.icon" class="h-4 w-4"></component>
<span>{{ tab.label }}</span>
</button>
</Tab>
</TabList>
<TabPanels class="mt-6 space-y-6 px-40" v-for="tab in tabItems" :key="tab.label">
<component :is="tab.tabComponent"></component>
</TabPanels>
</TabGroup>
</div>
</div>
</template>
<style scoped>
/* button {
transition: background 0.2s, color 0.2s;
} */
</style>