Added libs
This commit is contained in:
2
lockbox/.gitignore
vendored
Normal file
2
lockbox/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
__pycache__
|
||||
lockbox.zip
|
||||
1
lockbox/.mtimes.json
Normal file
1
lockbox/.mtimes.json
Normal 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
24
lockbox/api.http
Normal 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"}
|
||||
16
lockbox/browserExtension/hello.html
Normal file
16
lockbox/browserExtension/hello.html
Normal 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>
|
||||
BIN
lockbox/browserExtension/hello_extensions.png
Normal file
BIN
lockbox/browserExtension/hello_extensions.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 826 B |
19
lockbox/browserExtension/main.js
Normal file
19
lockbox/browserExtension/main.js
Normal 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);
|
||||
10
lockbox/browserExtension/manifest.json
Normal file
10
lockbox/browserExtension/manifest.json
Normal 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"
|
||||
}
|
||||
}
|
||||
6042
lockbox/browserExtension/socketio.js
Normal file
6042
lockbox/browserExtension/socketio.js
Normal file
File diff suppressed because it is too large
Load Diff
6
lockbox/config.json
Normal file
6
lockbox/config.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"server": {
|
||||
"host": "https://localhost",
|
||||
"port": "5001"
|
||||
}
|
||||
}
|
||||
30
lockbox/frontend/.gitignore
vendored
Normal file
30
lockbox/frontend/.gitignore
vendored
Normal 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
|
||||
3
lockbox/frontend/.vscode/extensions.json
vendored
Normal file
3
lockbox/frontend/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
29
lockbox/frontend/README.md
Normal file
29
lockbox/frontend/README.md
Normal 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
19
lockbox/frontend/ca.pem
Normal 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-----
|
||||
27
lockbox/frontend/ca_key.pem
Normal file
27
lockbox/frontend/ca_key.pem
Normal 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
19
lockbox/frontend/cert.pem
Normal 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-----
|
||||
13
lockbox/frontend/index.html
Normal file
13
lockbox/frontend/index.html
Normal 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>
|
||||
8
lockbox/frontend/jsconfig.json
Normal file
8
lockbox/frontend/jsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
27
lockbox/frontend/key.pem
Normal file
27
lockbox/frontend/key.pem
Normal 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
3778
lockbox/frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
lockbox/frontend/package.json
Normal file
28
lockbox/frontend/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
lockbox/frontend/public/favicon.ico
Normal file
BIN
lockbox/frontend/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
17
lockbox/frontend/src/App.vue
Normal file
17
lockbox/frontend/src/App.vue
Normal 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>
|
||||
1
lockbox/frontend/src/assets/logo.svg
Normal file
1
lockbox/frontend/src/assets/logo.svg
Normal 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 |
1
lockbox/frontend/src/assets/main.css
Normal file
1
lockbox/frontend/src/assets/main.css
Normal file
@@ -0,0 +1 @@
|
||||
@import "tailwindcss";
|
||||
29
lockbox/frontend/src/components/buttons/Button.vue
Normal file
29
lockbox/frontend/src/components/buttons/Button.vue
Normal 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>
|
||||
37
lockbox/frontend/src/components/buttons/ToogleSwitch.vue
Normal file
37
lockbox/frontend/src/components/buttons/ToogleSwitch.vue
Normal 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>
|
||||
12
lockbox/frontend/src/components/cards/Card.vue
Normal file
12
lockbox/frontend/src/components/cards/Card.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script setup>
|
||||
defineOptions({ inheritAttrs: false })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="rounded-xl border"
|
||||
:class="$attrs.class"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
9
lockbox/frontend/src/components/cards/CardContent.vue
Normal file
9
lockbox/frontend/src/components/cards/CardContent.vue
Normal 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>
|
||||
12
lockbox/frontend/src/components/cards/CardDescription.vue
Normal file
12
lockbox/frontend/src/components/cards/CardDescription.vue
Normal 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>
|
||||
9
lockbox/frontend/src/components/cards/CardFooter.vue
Normal file
9
lockbox/frontend/src/components/cards/CardFooter.vue
Normal 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>
|
||||
9
lockbox/frontend/src/components/cards/CardHeader.vue
Normal file
9
lockbox/frontend/src/components/cards/CardHeader.vue
Normal 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>
|
||||
12
lockbox/frontend/src/components/cards/CardTitle.vue
Normal file
12
lockbox/frontend/src/components/cards/CardTitle.vue
Normal 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>
|
||||
50
lockbox/frontend/src/components/inputs/InputText.vue
Normal file
50
lockbox/frontend/src/components/inputs/InputText.vue
Normal 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>
|
||||
12
lockbox/frontend/src/components/labels/Label.vue
Normal file
12
lockbox/frontend/src/components/labels/Label.vue
Normal 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>
|
||||
125
lockbox/frontend/src/components/tabs/TabCreateUser.vue
Normal file
125
lockbox/frontend/src/components/tabs/TabCreateUser.vue
Normal 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>
|
||||
245
lockbox/frontend/src/components/tabs/TabProofOfWork.vue
Normal file
245
lockbox/frontend/src/components/tabs/TabProofOfWork.vue
Normal 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>
|
||||
126
lockbox/frontend/src/components/tabs/TabSigningRequests.vue
Normal file
126
lockbox/frontend/src/components/tabs/TabSigningRequests.vue
Normal 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>
|
||||
201
lockbox/frontend/src/components/tabs/TabUsers.vue
Normal file
201
lockbox/frontend/src/components/tabs/TabUsers.vue
Normal 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>
|
||||
12
lockbox/frontend/src/main.js
Normal file
12
lockbox/frontend/src/main.js
Normal 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')
|
||||
3
lockbox/frontend/src/plugins/socketio.js
Normal file
3
lockbox/frontend/src/plugins/socketio.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { io } from 'socket.io-client'
|
||||
const socket = io('https://localhost:5001')
|
||||
export default socket
|
||||
21
lockbox/frontend/src/stores/auth.js
Normal file
21
lockbox/frontend/src/stores/auth.js
Normal 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
|
||||
}
|
||||
},
|
||||
})
|
||||
67
lockbox/frontend/src/views/Home.vue
Normal file
67
lockbox/frontend/src/views/Home.vue
Normal 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>
|
||||
23
lockbox/frontend/vite.config.js
Normal file
23
lockbox/frontend/vite.config.js
Normal 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))
|
||||
},
|
||||
},
|
||||
})
|
||||
52
lockbox/frontend/webView.py
Normal file
52
lockbox/frontend/webView.py
Normal 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
15
lockbox/info.json
Normal 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
154
lockbox/lockboxClient.py
Normal 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
168
lockbox/lockboxService.py
Normal 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")
|
||||
|
||||
170
lockbox/lockboxServiceApi.py
Normal file
170
lockbox/lockboxServiceApi.py
Normal 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
161
lockbox/miner.py
Normal 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(),
|
||||
})
|
||||
75
lockbox/minerApiBlueprint.py
Normal file
75
lockbox/minerApiBlueprint.py
Normal 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
26
lockbox/minerSocketio.py
Normal 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
2
lockbox/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
keyring
|
||||
pywebview
|
||||
3
lockbox/userHistory.json
Normal file
3
lockbox/userHistory.json
Normal file
@@ -0,0 +1,3 @@
|
||||
[
|
||||
"A4DZSk+TlR+4w39MbiIAQbti+N0H1QlJEhRH2DI6Iubj"
|
||||
]
|
||||
89
lockbox/utils.py
Normal file
89
lockbox/utils.py
Normal 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
|
||||
0
lockbox/vue/api/lockboxClientApi.js
Normal file
0
lockbox/vue/api/lockboxClientApi.js
Normal file
0
lockbox/vue/api/lockboxServiceApi.js
Normal file
0
lockbox/vue/api/lockboxServiceApi.js
Normal file
7
lockbox/vue/api/socketEvents.js
Normal file
7
lockbox/vue/api/socketEvents.js
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
export default function registerSocketEvents(socket) {
|
||||
socket.on('connect', (data) => {
|
||||
console.log('Connected LockboxClient')
|
||||
})
|
||||
}
|
||||
125
lockbox/vue/components/tabs/TabCreateUser.vue
Normal file
125
lockbox/vue/components/tabs/TabCreateUser.vue
Normal 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>
|
||||
259
lockbox/vue/components/tabs/TabProofOfWork.vue
Normal file
259
lockbox/vue/components/tabs/TabProofOfWork.vue
Normal 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>
|
||||
136
lockbox/vue/components/tabs/TabSigningRequests.vue
Normal file
136
lockbox/vue/components/tabs/TabSigningRequests.vue
Normal 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>
|
||||
219
lockbox/vue/components/tabs/TabUsers.vue
Normal file
219
lockbox/vue/components/tabs/TabUsers.vue
Normal 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>
|
||||
1
lockbox/vue/dependencies.txt
Normal file
1
lockbox/vue/dependencies.txt
Normal file
@@ -0,0 +1 @@
|
||||
@noble/secp256k1
|
||||
7
lockbox/vue/router.js
Normal file
7
lockbox/vue/router.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import HomeView from "./views/HomeView.vue";
|
||||
|
||||
const routes = [
|
||||
{path: '/', name:'lockbox', component: HomeView},
|
||||
]
|
||||
|
||||
export {routes};
|
||||
21
lockbox/vue/stores/auth.js
Normal file
21
lockbox/vue/stores/auth.js
Normal 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
|
||||
}
|
||||
},
|
||||
})
|
||||
59
lockbox/vue/views/HomeView.vue
Normal file
59
lockbox/vue/views/HomeView.vue
Normal 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>
|
||||
Reference in New Issue
Block a user