🎨 Expose node/peer metrics to API/UI. Set keylength/mtu from args

Expose to UI peernodes and on chain nodes. The on chain nodes are the
nodes which we are connected over in order to decode the blockchain. The
peer nodes are the node store list of the node.

Also allow to set packet-mtu and a desired keylength when generating
tokens.
This commit is contained in:
Ettore Di Giacinto
2022-01-22 12:10:23 +01:00
parent 5e69431105
commit 7968c5a362
16 changed files with 538 additions and 52 deletions

View File

@@ -1,4 +1,4 @@
// Copyright © 2021 Ettore Di Giacinto <mudler@mocaccino.org>
// Copyright © 2021-2022 Ettore Di Giacinto <mudler@mocaccino.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -22,8 +22,12 @@ import (
"net/http"
"time"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
apiTypes "github.com/mudler/edgevpn/api/types"
"github.com/labstack/echo/v4"
"github.com/mudler/edgevpn/pkg/blockchain"
"github.com/mudler/edgevpn/pkg/node"
"github.com/mudler/edgevpn/pkg/protocol"
"github.com/mudler/edgevpn/pkg/types"
)
@@ -40,7 +44,10 @@ func getFileSystem() http.FileSystem {
return http.FS(fsys)
}
func API(l string, defaultInterval, timeout time.Duration, ledger *blockchain.Ledger) error {
func API(ctx context.Context, l string, defaultInterval, timeout time.Duration, e *node.Node) error {
ledger, _ := e.Ledger()
ec := echo.New()
assetHandler := http.FileServer(getFileSystem())
@@ -60,20 +67,53 @@ func API(l string, defaultInterval, timeout time.Duration, ledger *blockchain.Le
machines := len(ledger.CurrentData()[protocol.MachinesLedgerKey])
users := len(ledger.CurrentData()[protocol.UsersLedgerKey])
services := len(ledger.CurrentData()[protocol.ServicesLedgerKey])
onChainNodes := len(e.HubRoom.Topic.ListPeers())
p2pPeers := len(e.Host().Network().Peerstore().Peers())
blockchain := ledger.Index()
return c.JSON(http.StatusOK, struct {
Files, Machines, Users, Services, BlockChain int
}{files, machines, users, services, blockchain})
Files, Machines, Users, Services, BlockChain, OnChainNodes, Peers int
}{files, machines, users, services, blockchain, onChainNodes, p2pPeers})
})
ec.GET("/api/machines", func(c echo.Context) error {
list := []*types.Machine{}
list := []*apiTypes.Machine{}
for _, v := range ledger.CurrentData()[protocol.MachinesLedgerKey] {
machine := &types.Machine{}
v.Unmarshal(machine)
list = append(list, machine)
m := &apiTypes.Machine{Machine: *machine}
if e.Host().Network().Connectedness(peer.ID(machine.PeerID)) == network.Connected {
m.Connected = true
}
for _, p := range e.HubRoom.Topic.ListPeers() {
if p.String() == machine.PeerID {
m.OnChain = true
}
}
list = append(list, m)
}
return c.JSON(http.StatusOK, list)
})
ec.GET("/api/nodes", func(c echo.Context) error {
list := []apiTypes.Peer{}
for _, v := range e.HubRoom.Topic.ListPeers() {
list = append(list, apiTypes.Peer{ID: v.String()})
}
return c.JSON(http.StatusOK, list)
})
ec.GET("/api/peerstore", func(c echo.Context) error {
list := []apiTypes.Peer{}
for _, v := range e.Host().Network().Peerstore().Peers() {
list = append(list, apiTypes.Peer{ID: v.String()})
}
e.HubRoom.Topic.ListPeers()
return c.JSON(http.StatusOK, list)
})
@@ -147,5 +187,16 @@ func API(l string, defaultInterval, timeout time.Duration, ledger *blockchain.Le
})
ec.HideBanner = true
return ec.Start(l)
if err := ec.Start(l); err != nil && err != http.ErrServerClosed {
return err
}
go func() {
<-ctx.Done()
ec.Shutdown(ctx)
}()
return nil
}

View File

@@ -53,6 +53,14 @@
<i class="fas fa-dice-d20"></i>&nbsp;
Blockchain
</a>
<a class="navbar-item" href="/peerstore.html">
<i class="fas fa-database"></i>&nbsp;
Peerstore
</a>
<a class="navbar-item" href="/nodes.html">
<i class="fas fa-ethernet"></i>&nbsp;
Nodes
</a>
</div>
<div class="navbar-end">
<div class="navbar-item">

View File

@@ -54,6 +54,14 @@
<i class="fas fa-dice-d20"></i>&nbsp;
Blockchain
</a>
<a class="navbar-item" href="/peerstore.html">
<i class="fas fa-database"></i>&nbsp;
Peerstore
</a>
<a class="navbar-item" href="/nodes.html">
<i class="fas fa-ethernet"></i>&nbsp;
Nodes
</a>
</div>
<div class="navbar-end">
<div class="navbar-item">

View File

@@ -54,6 +54,14 @@
<i class="fas fa-dice-d20"></i>&nbsp;
Blockchain
</a>
<a class="navbar-item" href="/peerstore.html">
<i class="fas fa-database"></i>&nbsp;
Peerstore
</a>
<a class="navbar-item" href="/nodes.html">
<i class="fas fa-ethernet"></i>&nbsp;
Nodes
</a>
</div>
<div class="navbar-end">
<div class="navbar-item">
@@ -83,37 +91,67 @@
</section>
<section class="section">
<div class="container">
<div class="tile is-ancestor has-text-centered">
<div class="tile is-ancestor has-text-centered row">
<div class="tile is-parent">
<article class="tile is-child box">
<a href="/machines.html">
<p class="title" id="machines"></p>
<p class="subtitle"><i class="fas fa-server"></i> Machines</p>
</a>
</article>
</div>
<div class="tile is-parent">
<article class="tile is-child box">
<a href="/services.html">
<p class="title" id="services"></p>
<p class="subtitle"><i class="fas fa-project-diagram"></i> Services</p>
</a>
</article>
</div>
<div class="tile is-parent">
<article class="tile is-child box">
<a href="/files.html">
<p class="title" id="files"></p>
<p class="subtitle"><i class="fas fa-file-upload"></i> Files</p>
</a>
</article>
</div>
</div>
<div class="tile is-ancestor has-text-centered row">
<div class="tile is-parent">
<article class="tile is-child box">
<a href="/users.html">
<p class="title" id="users"></p>
<p class="subtitle"><i class="fas fa-user-clock"></i> Users</p>
</a>
</article>
</div>
<div class="tile is-parent">
<article class="tile is-child box">
<a href="/blockchain.html">
<p class="title" id="blockchain"></p>
<p class="subtitle"><i class="fas fa-dice-d20"></i> Blockchain index</p>
</a>
</article>
</div>
<div class="tile is-parent">
<article class="tile is-child box">
<a href="/nodes.html">
<p class="title" id="nodes"></p>
<p class="subtitle"><i class="fas fa-ethernet"></i> On chain nodes</p>
</a>
</article>
</div>
<div class="tile is-parent">
<article class="tile is-child box">
<a href="/peerstore.html">
<p class="title" id="peers"></p>
<p class="subtitle"><i class="fas fa-database"></i> p2p known peers</p>
</a>
</article>
</div>
</div>
</div>
</div>
</section>
@@ -135,7 +173,8 @@
$('#files').html(response.Files);
$('#machines').html(response.Machines);
$('#blockchain').html(response.BlockChain);
$('#nodes').html(response.OnChainNodes);
$('#peers').html(response.Peers);
}
});
}

View File

@@ -53,6 +53,14 @@
<i class="fas fa-dice-d20"></i>&nbsp;
Blockchain
</a>
<a class="navbar-item" href="/peerstore.html">
<i class="fas fa-database"></i>&nbsp;
Peerstore
</a>
<a class="navbar-item" href="/nodes.html">
<i class="fas fa-ethernet"></i>&nbsp;
Nodes
</a>
</div>
<div class="navbar-end">
<div class="navbar-item">
@@ -96,6 +104,8 @@
<th ><abbr title="OS">OS</abbr></th>
<th><abbr title="Arch">Architecture</abbr></th>
<th><abbr title="Version">Version</abbr></th>
<th><abbr title="Connected">Connected</abbr></th>
<th><abbr title="OnChain">OnChain</abbr></th>
</tr>
</thead>
@@ -125,7 +135,14 @@
{ "data": "OS" },
{ "data": "Arch" },
{ "data": "Version" },
{ "data": "Connected" },
{ "data": "OnChain" },
],
"columnDefs": [ {
"targets": -1,
"data": null,
"defaultContent": "<button>Click!</button>"
} ],
} );
setInterval( function () {

144
api/public/nodes.html Normal file
View File

@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="/css/fonts.css" rel="stylesheet">
<link rel="stylesheet" href="/css/bulma.min.css" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.4/css/all.css" integrity="sha384-DyZ88mC6Up2uqS4h/KRgHuoeGwBcD4Ng9SiP4dIRy0EXTlnuz47vAwmeGwVChigm" crossorigin="anonymous">
<script src="/js/jquery.min.js"></script>
<link rel="stylesheet" type="text/css" href="/css/dt.min.css">
<script type="text/javascript" charset="utf8" src="/js/dt.min.js"></script>
<script src="/js/dt.js"></script>
<script src="/js/common.js"></script>
<link rel="stylesheet" type="text/css" href="/css/style.css">
<title>EdgeVPN - Nodes index</title>
</head>
<body>
<nav class="navbar is-light is-spaced has-shadow" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="/">
<img src="/images/logo.png" > &nbsp;
EdgeVPN
</a>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="menu">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="menu" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="/machines.html">
<i class="fas fa-server"></i>&nbsp;
Machines
</a>
<a class="navbar-item" href="/services.html">
<i class="fas fa-project-diagram"></i>&nbsp;
Services
</a>
<a class="navbar-item" href="/files.html">
<i class="fas fa-file-upload"></i>&nbsp;
Files
</a>
<a class="navbar-item" href="/users.html">
<i class="fas fa-user-clock"></i>&nbsp;
Users
</a>
<a class="navbar-item" href="/blockchain.html">
<i class="fas fa-dice-d20"></i>&nbsp;
Blockchain
</a>
<a class="navbar-item" href="/peerstore.html">
<i class="fas fa-database"></i>&nbsp;
Peerstore
</a>
<a class="navbar-item" href="/nodes.html">
<i class="fas fa-ethernet"></i>&nbsp;
Nodes
</a>
</div>
<div class="navbar-end">
<div class="navbar-item">
<div class="buttons">
<a class="button is-link" href="https://github.com/mudler/edgevpn" target=_blank>
<strong><i class="fab fa-github-alt"></i> GitHub</strong>
</a>
<a class="button is-light" href="https://github.com/mudler/edgevpn/issues/new" target=_blank>
<i class="fas fa-bug"></i>&nbsp; Report issue
</a>
</div>
</div>
</div>
</div>
</nav>
<section class="hero">
<div class="hero-body">
<div class="container">
<h1 class="title">
<i class="fas fa-database"></i> Nodes connected via p2p
</h1>
<h2 class="subtitle">
Accessible via api at <a href="/api/nodes" target=_blank><code> /api/nodes </code></a>
</h2>
</div>
</div>
</section>
<section class="section">
<div class="container">
<table data-toggle="table"
data-search="true"
data-show-columns="true"
id="table" >
<thead>
<tr>
<th ><abbr title="ID">PeerID</abbr></th>
</tr>
</thead>
</table>
</div>
</section>
<script type="text/javascript">
$(document).ready(function() {
var table = $('#table').DataTable( {
"processing": true,
"ajax": {
"url": "/api/nodes",
"type": "GET",
"dataSrc": '',
},
'language':{
"loadingRecords": "",
"processing": ""
},
"columns": [
{ "data": "ID" },
],
} );
setInterval( function () {
table.ajax.reload();
}, 5000 ); // 5 s
} );
</script>
<footer class="footer">
<div class="content has-text-centered">
<p>
<strong>EdgeVPN</strong> by <a href="https://github.com/mudler/edgevpn">Ettore Di Giacinto</a>. The source code is licensed
<a href="https://github.com/mudler/edgevpn/blob/master/LICENSE">GPLv3</a>.
Logo originally made by <a href="https://www.flaticon.com/authors/uniconlabs" title="Uniconlabs">Uniconlabs</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
</p>
</div>
</footer>
</body>
</html>

144
api/public/peerstore.html Normal file
View File

@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="/css/fonts.css" rel="stylesheet">
<link rel="stylesheet" href="/css/bulma.min.css" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.4/css/all.css" integrity="sha384-DyZ88mC6Up2uqS4h/KRgHuoeGwBcD4Ng9SiP4dIRy0EXTlnuz47vAwmeGwVChigm" crossorigin="anonymous">
<script src="/js/jquery.min.js"></script>
<link rel="stylesheet" type="text/css" href="/css/dt.min.css">
<script type="text/javascript" charset="utf8" src="/js/dt.min.js"></script>
<script src="/js/dt.js"></script>
<script src="/js/common.js"></script>
<link rel="stylesheet" type="text/css" href="/css/style.css">
<title>EdgeVPN - p2p node store</title>
</head>
<body>
<nav class="navbar is-light is-spaced has-shadow" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="/">
<img src="/images/logo.png" > &nbsp;
EdgeVPN
</a>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="menu">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="menu" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="/machines.html">
<i class="fas fa-server"></i>&nbsp;
Machines
</a>
<a class="navbar-item" href="/services.html">
<i class="fas fa-project-diagram"></i>&nbsp;
Services
</a>
<a class="navbar-item" href="/files.html">
<i class="fas fa-file-upload"></i>&nbsp;
Files
</a>
<a class="navbar-item" href="/users.html">
<i class="fas fa-user-clock"></i>&nbsp;
Users
</a>
<a class="navbar-item" href="/blockchain.html">
<i class="fas fa-dice-d20"></i>&nbsp;
Blockchain
</a>
<a class="navbar-item" href="/peerstore.html">
<i class="fas fa-database"></i>&nbsp;
Peerstore
</a>
<a class="navbar-item" href="/nodes.html">
<i class="fas fa-ethernet"></i>&nbsp;
Nodes
</a>
</div>
<div class="navbar-end">
<div class="navbar-item">
<div class="buttons">
<a class="button is-link" href="https://github.com/mudler/edgevpn" target=_blank>
<strong><i class="fab fa-github-alt"></i> GitHub</strong>
</a>
<a class="button is-light" href="https://github.com/mudler/edgevpn/issues/new" target=_blank>
<i class="fas fa-bug"></i>&nbsp; Report issue
</a>
</div>
</div>
</div>
</div>
</nav>
<section class="hero">
<div class="hero-body">
<div class="container">
<h1 class="title">
<i class="fas fa-database"></i> P2p peer store list
</h1>
<h2 class="subtitle">
Accessible via api at <a href="/api/peerstore" target=_blank><code> /api/peerstore </code></a>
</h2>
</div>
</div>
</section>
<section class="section">
<div class="container">
<table data-toggle="table"
data-search="true"
data-show-columns="true"
id="table" >
<thead>
<tr>
<th ><abbr title="ID">PeerID</abbr></th>
</tr>
</thead>
</table>
</div>
</section>
<script type="text/javascript">
$(document).ready(function() {
var table = $('#table').DataTable( {
"processing": true,
"ajax": {
"url": "/api/peerstore",
"type": "GET",
"dataSrc": '',
},
'language':{
"loadingRecords": "",
"processing": ""
},
"columns": [
{ "data": "ID" },
],
} );
setInterval( function () {
table.ajax.reload();
}, 5000 ); // 5 s
} );
</script>
<footer class="footer">
<div class="content has-text-centered">
<p>
<strong>EdgeVPN</strong> by <a href="https://github.com/mudler/edgevpn">Ettore Di Giacinto</a>. The source code is licensed
<a href="https://github.com/mudler/edgevpn/blob/master/LICENSE">GPLv3</a>.
Logo originally made by <a href="https://www.flaticon.com/authors/uniconlabs" title="Uniconlabs">Uniconlabs</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
</p>
</div>
</footer>
</body>
</html>

View File

@@ -54,6 +54,14 @@
<i class="fas fa-dice-d20"></i>&nbsp;
Blockchain
</a>
<a class="navbar-item" href="/peerstore.html">
<i class="fas fa-database"></i>&nbsp;
Peerstore
</a>
<a class="navbar-item" href="/nodes.html">
<i class="fas fa-ethernet"></i>&nbsp;
Nodes
</a>
</div>
<div class="navbar-end">
<div class="navbar-item">

View File

@@ -53,6 +53,14 @@
<i class="fas fa-dice-d20"></i>&nbsp;
Blockchain
</a>
<a class="navbar-item" href="/peerstore.html">
<i class="fas fa-database"></i>&nbsp;
Peerstore
</a>
<a class="navbar-item" href="/nodes.html">
<i class="fas fa-ethernet"></i>&nbsp;
Nodes
</a>
</div>
<div class="navbar-end">
<div class="navbar-item">

24
api/types/machine.go Normal file
View File

@@ -0,0 +1,24 @@
// Copyright © 2022 Ettore Di Giacinto <mudler@mocaccino.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package types
import "github.com/mudler/edgevpn/pkg/types"
type Machine struct {
types.Machine
Connected bool
OnChain bool
}

20
api/types/peer.go Normal file
View File

@@ -0,0 +1,20 @@
// Copyright © 2022 Ettore Di Giacinto <mudler@mocaccino.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package types
type Peer struct {
ID string
}

View File

@@ -44,13 +44,13 @@ A simple UI interface is available to display network data.`,
displayStart(ll)
ctx := context.Background()
// Start the node to the network, using our ledger
if err := e.Start(context.Background()); err != nil {
if err := e.Start(ctx); err != nil {
return err
}
ledger, _ := e.Ledger()
return api.API(c.String("listen"), 5*time.Second, 20*time.Second, ledger)
return api.API(ctx, c.String("listen"), 5*time.Second, 20*time.Second, e)
},
}
}

View File

@@ -34,7 +34,11 @@ under certain conditions.`
func MainFlags() []cli.Flag {
return append([]cli.Flag{
&cli.IntFlag{
Name: "key-otp-interval",
Usage: "Tweaks default otp interval (in seconds) when generating new tokens",
Value: 9000,
},
&cli.BoolFlag{
Name: "g",
Usage: "Generates a new configuration and prints it on screen",
@@ -75,7 +79,7 @@ func Main() func(c *cli.Context) error {
return func(c *cli.Context) error {
if c.Bool("g") {
// Generates a new config and exit
newData := edgevpn.GenerateNewConnectionData()
newData := edgevpn.GenerateNewConnectionData(c.Int("key-otp-interval"))
if c.Bool("b") {
fmt.Print(newData.Base64())
} else {
@@ -96,15 +100,11 @@ func Main() func(c *cli.Context) error {
displayStart(ll)
ledger, err := e.Ledger()
if err != nil {
return err
}
ctx := context.Background()
if c.Bool("api") {
go api.API(c.String("api-listen"), 5*time.Second, 20*time.Second, ledger)
go api.API(ctx, c.String("api-listen"), 5*time.Second, 20*time.Second, e)
}
return e.Start(context.Background())
return e.Start(ctx)
}
}

View File

@@ -50,6 +50,12 @@ var CommonFlags []cli.Flag = []cli.Flag{
EnvVar: "EDGEVPNMTU",
Value: 1200,
},
&cli.IntFlag{
Name: "packet-mtu",
Usage: "Specify a mtu",
EnvVar: "EDGEVPNPACKETMTU",
Value: 1420,
},
&cli.IntFlag{
Name: "channel-buffer-size",
Usage: "Specify a channel buffer size",
@@ -226,7 +232,7 @@ func cliToOpts(c *cli.Context) ([]node.Option, []vpn.Option, *logger.Logger) {
vpn.NetLinkBootstrap(true),
vpn.WithChannelBufferSize(c.Int("channel-buffer-size")),
vpn.WithInterfaceMTU(c.Int("mtu")),
vpn.WithPacketMTU(1420),
vpn.WithPacketMTU(c.Int("packet-mtu")),
vpn.WithRouterAddress(router),
vpn.WithInterfaceName(iface),
}

View File

@@ -36,7 +36,7 @@ type Room struct {
ctx context.Context
ps *pubsub.PubSub
topic *pubsub.Topic
Topic *pubsub.Topic
sub *pubsub.Subscription
roomName string
@@ -61,7 +61,7 @@ func JoinRoom(ctx context.Context, ps *pubsub.PubSub, selfID peer.ID, roomName s
cr := &Room{
ctx: ctx,
ps: ps,
topic: topic,
Topic: topic,
sub: sub,
self: selfID,
roomName: roomName,
@@ -94,7 +94,7 @@ func (cr *Room) PublishMessage(m *Message) error {
if err != nil {
return err
}
return cr.topic.Publish(cr.ctx, msgBytes)
return cr.Topic.Publish(cr.ctx, msgBytes)
}
// readLoop pulls messages from the pubsub topic and pushes them onto the Messages channel.

View File

@@ -246,21 +246,30 @@ func (y YAMLConnectionConfig) copy(mdns, dht bool, cfg *Config) {
const defaultKeyLength = 32
func GenerateNewConnectionData() *YAMLConnectionConfig {
func GenerateNewConnectionData(i ...int) *YAMLConnectionConfig {
defaultInterval := 9000
maxMessSize := 20 << 20 // 20MB
if len(i) >= 2 {
defaultInterval = i[0]
maxMessSize = i[1]
} else if len(i) == 1 {
defaultInterval = i[0]
}
return &YAMLConnectionConfig{
MaxMessageSize: 20 << 20, // 20MB
MaxMessageSize: maxMessSize,
RoomName: utils.RandStringRunes(defaultKeyLength),
Rendezvous: utils.RandStringRunes(defaultKeyLength),
MDNS: utils.RandStringRunes(defaultKeyLength),
OTP: OTP{
DHT: OTPConfig{
Key: gotp.RandomSecret(defaultKeyLength),
Interval: 9000,
Interval: defaultInterval,
Length: defaultKeyLength,
},
Crypto: OTPConfig{
Key: gotp.RandomSecret(defaultKeyLength),
Interval: 9000,
Interval: defaultInterval,
Length: defaultKeyLength,
},
},