Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 590f14d101 | |||
| d824b3ef72 | |||
| 5b1c375934 | |||
| 7080ea74fb | |||
| 97d1d6e59b | |||
| 9a858c8993 | |||
| e003288004 | |||
| d2c97bd5e5 | |||
| 1ec2db9583 | |||
| b8cd76b481 | |||
| 7380362280 | |||
| 34a365960a | |||
| a52070dfa6 | |||
| 76510be028 | |||
| a1fec08cc4 | |||
| 556cc85cde | |||
| 2ebac1676f | |||
| b6749bdb29 | |||
| 1b141173cc | |||
| 06174de597 | |||
| e6523e0a7b | |||
| 78da3c37c0 | |||
| 3d305a0d70 | |||
| 6166e930fe | |||
| 5edbfa4ce4 | |||
| 1071d9e5ee | |||
| 017291f784 | |||
| 17a7a09ab0 | |||
| 3a6fd107c0 | |||
| 4c31950a88 | |||
| aa71cb1d76 | |||
| c746675045 | |||
| 4052995ba8 | |||
| 44f48fced8 | |||
| c78e89a94b | |||
| 01842dbc8d | |||
| cec35fc16b | |||
| e5fb6e7231 | |||
| 6c017b3262 | |||
| 2e64c26a0a | |||
| c8b9556bcd | |||
| 5c93f4e570 | |||
| dfc3d4e579 | |||
| e7b2683d2c | |||
| 3dd79af7de | |||
| d86054f3a3 | |||
| 547e143f0c | |||
| 48478c49bc | |||
| ba58c7d744 | |||
| 32cf9cb581 | |||
| 29a1061d18 | |||
| 13be73f7e2 | |||
| 38586187e9 | |||
| af987008b0 | |||
| ba393ec55a | |||
| a85fc61e16 | |||
| 7b629b4b0d | |||
| 72923a34ff | |||
| fd012eea00 | |||
| 2aeef9925d | |||
| 75075b81fb | |||
| 0e88cddc24 | |||
| 6dc9100b45 | |||
| 1863e82595 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
/.clj-kondo/
|
/.clj-kondo/
|
||||||
/.cpcache/
|
/**/.cpcache/
|
||||||
/.lsp/
|
/.lsp/
|
||||||
/target/
|
/target/
|
||||||
.nrepl-port
|
.nrepl-port
|
||||||
|
|||||||
170
README.md
170
README.md
@@ -1,6 +1,7 @@
|
|||||||
# clj-totp
|
# clj-totp
|
||||||
|
|
||||||
TOTP (Time-based One Time Password) in clojure. It can be used in the command line, web API o simple embedded web.
|
TOTP (Time-based One Time Password) in clojure. It supports several digest algorithms and length.
|
||||||
|
|
||||||
|
|
||||||
## What is TOPT
|
## What is TOPT
|
||||||
|
|
||||||
@@ -14,27 +15,182 @@ You can read more about the algorithm here:
|
|||||||
- HOTP RFC: https://www.ietf.org/rfc/rfc4226.txt
|
- HOTP RFC: https://www.ietf.org/rfc/rfc4226.txt
|
||||||
|
|
||||||
|
|
||||||
## The inside
|
## How to use
|
||||||
|
|
||||||
This project is done 100% in clojure. It uses `deps.edn` for configuring the project and `build.clj` for compiling.
|

|
||||||
|
|
||||||
## Implementation timeline
|
First, you must have installed a Java Runtime Environment. Check https://adoptium.net/es if you are
|
||||||
|
unsure how to install.
|
||||||
|
|
||||||
|
The project is distributed as a jar file with all dependencies included (a.k.a. "uberjar"), and uses
|
||||||
|
a simple script to launch the program. Script and uberjar must be in the same directory.
|
||||||
|
|
||||||
|
Execute without parameters to show main commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clj-totp.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
A quick description of each command:
|
||||||
|
|
||||||
|
- `generate`: Show a TOTP with a given secret and parameters, not stored in config.
|
||||||
|
- `config`: Manage configuration file
|
||||||
|
- `add`: Store a new TOTP configuration
|
||||||
|
- `delete`: Delete an stored configuration
|
||||||
|
- `list`: Shows a list of TOPT stored configurations
|
||||||
|
- `get`: Generate a TOTP previously added
|
||||||
|
- `import`: Import a URL with the TOTP configuration
|
||||||
|
|
||||||
|
|
||||||
|
### Quick and simple generation
|
||||||
|
|
||||||
|
If you want to quickly generate a TOPT, you only need to suministrate the secret in B32 format:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clj-totp.sh generate <B32 secret>
|
||||||
|
```
|
||||||
|
|
||||||
|
The OTP changes every 30 seconds, you can print every change with `-c` option:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clj-totp.sh generate -c <B32 secret>
|
||||||
|
```
|
||||||
|
|
||||||
|
It will update the TOTP every 30 seconds, until you press `<Enter>` or `<ctrl+c>`.
|
||||||
|
|
||||||
|
|
||||||
|
### Store your configurations
|
||||||
|
|
||||||
|
Writing the B32 secret each time can be a bit tedious, but you can store secrets for your applications.
|
||||||
|
Be carefull, this version **don't encrypt passwords**, secrets are saved in plain text in a file in
|
||||||
|
your home dir.
|
||||||
|
|
||||||
|
With the `config` command you can check your configuration file. Now, let's explorer the subcommands:
|
||||||
|
|
||||||
|
Check if the config file exists, and show the full path:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clj-totp.sh info config
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a new config file, if the file exists, it will prompt you if you want to overwrite it:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
clj-totp.sh info init
|
||||||
|
```
|
||||||
|
|
||||||
|
Show all data contained in the config file, as a table:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
clj-totp.sh info show
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Use stored configurations
|
||||||
|
|
||||||
|
If you have a valid configuration file, it's time to configure some applications.
|
||||||
|
|
||||||
|
To add a new configured application, you can use the `add` command. The simplest way to add a new
|
||||||
|
configuration is to specify an alias and the secret in B32:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
clj-totp.sh add <alias> <b32 secret>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `add` subcommand has a lot of configuration options, you can explore them wit the `-?` param.
|
||||||
|
|
||||||
|
To list all added configurations, use the `list` command:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
clj-totp.sh list
|
||||||
|
```
|
||||||
|
|
||||||
|
If you made a mistake, you can delete a configured app with the `delete` command:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
clj-totp.sh delete <alias>
|
||||||
|
```
|
||||||
|
|
||||||
|
When you have some configured apps, it's time to use them, with the `get` command. To generate a
|
||||||
|
single TOTP for some app you can simple pass a list of alias:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
clj-totp.sh get <alias1> <alias2> <aliasN>
|
||||||
|
```
|
||||||
|
|
||||||
|
It will show the TOTP value at the current time, but TOTPs changes every 30 seconds, to show
|
||||||
|
the value when it changes, add the `-c` param. It will update the TOTP for each alias until you
|
||||||
|
press enter key (or <Ctrl-C>):
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
clj-totp.sh get <alias1> <alias2> <aliasN> -c
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, this program has an `import` command, that can import from a decoded QR or exported data
|
||||||
|
from Google Autenticator:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
clj-totp.sh import <alias> "<url>"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Project's plan
|
||||||
|
|
||||||
### v1.0
|
### v1.0
|
||||||
- [x] Functional TOTP generation
|
- [x] Functional TOTP generation
|
||||||
- [x] Get TOTP from command line
|
- [x] Get TOTP from command line
|
||||||
- [x] Continuous generation
|
- [x] Continuous generation
|
||||||
- [ ] Store configuration in a properties file or simple DB
|
|
||||||
|
|
||||||
### v1.1
|
### v1.1
|
||||||
|
- [x] Store configuration in a properties file or simple DB
|
||||||
|
- [x] Import from `otpauth` and `otpauth-migration` protocols
|
||||||
|
- [x] Show several OTPs at once
|
||||||
|
|
||||||
|
### v1.2
|
||||||
|
- [x] Show progress bar
|
||||||
|
- [x] Styles for progress bar
|
||||||
|
- [x] Native compilation script corrections
|
||||||
|
|
||||||
|
### v2
|
||||||
|
- [x] Restructurate as a multiproject
|
||||||
- [ ] REST API
|
- [ ] REST API
|
||||||
- [ ] User management
|
- [ ] User management
|
||||||
- [ ] Robust BD backend (H2, datomic, or similar)
|
- [ ] Robust BD backend (H2, datomic, or similar)
|
||||||
|
|
||||||
### v1.2
|
### v3
|
||||||
- [ ] Simple web connected to REST API
|
- [ ] Simple web connected to REST API
|
||||||
|
|
||||||
|
|
||||||
## Ideas
|
## Ideas
|
||||||
- Import from google auth URL: https://github.com/dim13/otpauth
|
|
||||||
|
Some ideas for future versions:
|
||||||
|
|
||||||
- Store passwords securely: https://github.com/weavejester/crypto-password
|
- Store passwords securely: https://github.com/weavejester/crypto-password
|
||||||
|
|
||||||
|
|
||||||
|
## Building the project
|
||||||
|
|
||||||
|
This project is done 100% in clojure. It uses `deps.edn` for configuring the project and `build.clj`
|
||||||
|
for defining compilation tasks.
|
||||||
|
|
||||||
|
The first step is to install Java JDK, version 11 or newer (version 21 recommended).
|
||||||
|
|
||||||
|
To execute manually the main function, simple use the `:run` alias:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clojure -M:run/cli <commands and parameters>
|
||||||
|
```
|
||||||
|
|
||||||
|
To build the uberjar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clojure -T:build :uber/cli
|
||||||
|
```
|
||||||
|
|
||||||
|
There is a utility script to build a native executable using Graal VM. Please, edit the script and
|
||||||
|
check the path to your Graal installation. Use it at your own risk.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
native.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
189
build.clj
189
build.clj
@@ -1,25 +1,174 @@
|
|||||||
(ns build
|
(ns build
|
||||||
(:require [clojure.tools.build.api :as b]))
|
(:refer-clojure :exclude [test])
|
||||||
|
(:require [clojure.tools.build.api :as b]
|
||||||
|
[clojure.java.io :as io]
|
||||||
|
[clojure.pprint :as pp]
|
||||||
|
[clojure.java.basis :as basis]))
|
||||||
|
|
||||||
(def lib 'es.rcorral/clj-topt)
|
(def lib-group "es.rcorral")
|
||||||
(def version (format "1.0.%s" (b/git-count-revs nil)))
|
(def artifact-prefix "clj-totp")
|
||||||
(def class-dir "target/classes")
|
(def subprojs-base "projects")
|
||||||
(def uber-file (format "target/%s-%s-standalone.jar" (name lib) version))
|
(def curr-version (format "2.0.%s" (b/git-count-revs nil)))
|
||||||
|
|
||||||
;; delay to defer side effects (artifact downloads)
|
|
||||||
(def basis (delay (b/create-basis {:project "deps.edn"})))
|
|
||||||
|
|
||||||
(defn clean [_]
|
;; Builds artifact's full descriptor for each subproject
|
||||||
(b/delete {:path "target"}))
|
(defn lib [subproj]
|
||||||
|
(symbol (str lib-group "/" artifact-prefix "-" subproj )))
|
||||||
|
|
||||||
(defn uber [_]
|
|
||||||
(clean nil)
|
;; Basis for each subproject, using their own deps.edn
|
||||||
(b/copy-dir {:src-dirs ["src" "resources"]
|
;; Injects :extra-deps from :build as additional dependencies
|
||||||
:target-dir class-dir})
|
(defn basis [subproj]
|
||||||
(b/compile-clj {:basis @basis
|
(delay (b/create-basis {:project (str subprojs-base "/" subproj "/deps.edn")
|
||||||
:ns-compile '[totp.app]
|
;; Inject extra deps as deps
|
||||||
:class-dir class-dir})
|
:extra {:deps (get-in (basis/initial-basis) [:aliases :build :extra-deps])}
|
||||||
(b/uber {:class-dir class-dir
|
})))
|
||||||
:uber-file uber-file
|
|
||||||
:basis @basis
|
|
||||||
:main 'totp.app}))
|
;; Show basis generated for a subproject
|
||||||
|
#_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]}
|
||||||
|
(defn show-basis [subproj]
|
||||||
|
(println (with-out-str
|
||||||
|
(pp/pprint
|
||||||
|
@(basis subproj)
|
||||||
|
;(basis/initial-basis)
|
||||||
|
))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(pp/pprint (keys (basis/initial-basis)))
|
||||||
|
(pp/pprint (:deps (basis/initial-basis)))
|
||||||
|
(pp/pprint (:libs (basis/initial-basis)))
|
||||||
|
(pp/pprint (sort (keys (:aliases (basis/initial-basis)))))
|
||||||
|
(get-in (basis/initial-basis) [:aliases :build :extra-deps])
|
||||||
|
)
|
||||||
|
|
||||||
|
;; Target dir for each subproject
|
||||||
|
(defn target-dir [subproj]
|
||||||
|
(str "target/" subproj))
|
||||||
|
|
||||||
|
|
||||||
|
;; Path for compiled classes
|
||||||
|
(defn class-dir [subproj]
|
||||||
|
(str (target-dir subproj) "/" "classes"))
|
||||||
|
|
||||||
|
|
||||||
|
;; Jar file for each subproject. :uber type adds -standalone suffix
|
||||||
|
(defn jar-file [subproj version type]
|
||||||
|
(format "target/%s-%s-%s%s.jar" artifact-prefix subproj version
|
||||||
|
(if (= type :uber) "-standalone" "")))
|
||||||
|
|
||||||
|
|
||||||
|
;; Clean target dir for subproject
|
||||||
|
(defn clean [{:keys [subproj]}]
|
||||||
|
(b/delete {:path (target-dir subproj)})
|
||||||
|
(println "Project" subproj "cleaned"))
|
||||||
|
|
||||||
|
|
||||||
|
;; Compile java classes, only if java subdir exists
|
||||||
|
(defn compile-java [subproj]
|
||||||
|
(let [java-dir (str subprojs-base "/" subproj "/java")]
|
||||||
|
(when (.exists (io/file java-dir))
|
||||||
|
(println "Compiling java code for" subproj)
|
||||||
|
(b/javac {:src-dirs [java-dir]
|
||||||
|
:class-dir (class-dir subproj)
|
||||||
|
:basis @(basis subproj)
|
||||||
|
:javac-opts ["-source" "11" "--target" "11" "-proc:none"]}))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Create a jar file
|
||||||
|
(defn jar
|
||||||
|
"Build a simple jar file, with no dependencies included."
|
||||||
|
[{:keys [subproj version]
|
||||||
|
:or {version curr-version}}]
|
||||||
|
(let [target-dir (target-dir subproj)
|
||||||
|
class-dir (class-dir subproj)
|
||||||
|
src-dir (str subprojs-base "/" subproj "/src")
|
||||||
|
resources-dir (str subprojs-base "/" subproj "/resources")
|
||||||
|
basis (basis subproj)
|
||||||
|
jar-file (jar-file subproj version :plain)]
|
||||||
|
;; Clean only class dir
|
||||||
|
(b/delete {:path class-dir})
|
||||||
|
;; Copy code
|
||||||
|
(b/copy-dir {:src-dirs [src-dir]
|
||||||
|
:target-dir class-dir})
|
||||||
|
;; Copy resources
|
||||||
|
(b/copy-dir {:src-dirs [resources-dir]
|
||||||
|
:target-dir target-dir})
|
||||||
|
;; Compile java code, if exists
|
||||||
|
(compile-java subproj)
|
||||||
|
;; Build jar
|
||||||
|
(b/jar {:class-dir class-dir
|
||||||
|
:basis @basis
|
||||||
|
:jar-file jar-file
|
||||||
|
:lib (lib subproj)
|
||||||
|
:version version})
|
||||||
|
(println "Generated jar file:" jar-file)))
|
||||||
|
|
||||||
|
|
||||||
|
;; Create an uber jar, with all dependencies inside
|
||||||
|
#_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]}
|
||||||
|
(defn uber
|
||||||
|
"Build a uberjar with all dependencies included"
|
||||||
|
[{:keys [subproj version main-ns]
|
||||||
|
:or {version curr-version}}]
|
||||||
|
(let [target-dir (target-dir subproj)
|
||||||
|
basis (basis subproj)
|
||||||
|
class-dir (class-dir subproj)
|
||||||
|
src-dir (str subprojs-base "/" subproj "/src")
|
||||||
|
resources-dir (str subprojs-base "/" subproj "/resources")
|
||||||
|
uber-file (jar-file subproj version :uber)]
|
||||||
|
;(println "Using basis: ")(show-basis subproj)
|
||||||
|
(b/delete {:path class-dir})
|
||||||
|
(b/copy-dir {:src-dirs [src-dir]
|
||||||
|
:target-dir class-dir})
|
||||||
|
(b/copy-dir {:src-dirs [resources-dir]
|
||||||
|
:target-dir target-dir})
|
||||||
|
(compile-java subproj)
|
||||||
|
(b/compile-clj {:basis @basis
|
||||||
|
:src-dirs [src-dir] :class-dir class-dir})
|
||||||
|
(b/uber {:class-dir class-dir
|
||||||
|
:uber-file uber-file
|
||||||
|
:basis @basis
|
||||||
|
:main main-ns})
|
||||||
|
(println "Generated uberjar executable:" uber-file)))
|
||||||
|
|
||||||
|
|
||||||
|
;; Multimethod to get the name of all subdirs in a dir.
|
||||||
|
;; Accepts strings or files
|
||||||
|
(defmulti get-subdirs type)
|
||||||
|
|
||||||
|
(defmethod get-subdirs
|
||||||
|
java.lang.String [dir]
|
||||||
|
(get-subdirs (io/file dir)))
|
||||||
|
|
||||||
|
(defmethod get-subdirs
|
||||||
|
java.io.File [dir]
|
||||||
|
(if (.isDirectory dir)
|
||||||
|
(filter #(.isDirectory %) (.listFiles dir))
|
||||||
|
(println "Directory" subprojs-base "doesn't exists!")))
|
||||||
|
|
||||||
|
|
||||||
|
;; Get the name of all subdir in a given directory
|
||||||
|
(defn get-subdir-names
|
||||||
|
"Get a list projects in the 'projects' directory"
|
||||||
|
[dir-name]
|
||||||
|
(map #(.getName %) (get-subdirs dir-name)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(get-subdirs "projects")
|
||||||
|
(get-subdirs (io/file "projects"))
|
||||||
|
(get-subdir-names "projects")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; Generate jar files for all projects
|
||||||
|
(defn jar-all
|
||||||
|
"Build jar files for all projects"
|
||||||
|
[& {:keys [version]
|
||||||
|
:or {version curr-version}}]
|
||||||
|
(dorun (map #(jar {:subproj % :version version}) (get-subdir-names subprojs-base))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(jar-all )
|
||||||
|
|
||||||
|
)
|
||||||
42
clj-totp.iml
42
clj-totp.iml
@@ -1,42 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="JAVA_MODULE" version="4">
|
|
||||||
<component name="BuildSystem">
|
|
||||||
<option name="buildSystemId" value="CLOJURE_DEPS" />
|
|
||||||
<option name="displayName" value="clj-totp" />
|
|
||||||
</component>
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="library" name="Deps: org.clojure/clojure:1.12.1" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: lambdaisland/deep-diff2:2.11.216" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: org.clojure/core.specs.alpha:0.4.74" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: lambdaisland/kaocha:1.91.1392" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: expound:0.9.0" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: org.clojure/spec.alpha:0.5.238" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: org.clojure/tools.cli:1.1.230" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: lambdaisland/clj-diff:1.4.78" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: net.incongru.watchservice/barbary-watchservice:1.0" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: slingshot:0.12.2" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: fipp:0.6.26" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: com.nextjournal/beholder:1.0.2" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: aero:1.1.6" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: lambdaisland/tools.namespace:0.3.256" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: mvxcvi/arrangement:2.1.0" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: io.methvin/directory-watcher:0.17.3" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: progrock:0.1.2" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: org.clojure/java.classpath:1.0.0" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: clojure.java-time:1.4.3" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: org.clojure/core.rrb-vector:0.1.2" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: net.java.dev.jna/jna:5.12.1" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: org.clojure/tools.reader:1.3.6" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: org.tcrawley/dynapath:1.1.0" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: org.slf4j/slf4j-api:1.7.36" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: hawk:0.2.11" level="project" />
|
|
||||||
<orderEntry type="library" name="Deps: meta-merge:1.0.0" level="project" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@@ -1 +1,11 @@
|
|||||||
~/.sdkman/candidates/java/21.0.2-graalce/bin/java -agentlib:native-image-agent=config-output-dir=META-INF/native-image -jar target/clj-topt-1.0.32-standalone.jar g TUGOBTEHPSCMUCYAT6UPELNWGE -c
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
JAVA_CMD=~/.sdkman/candidates/java/21.0.2-graalce/bin/java
|
||||||
|
UBERJAR=$(realpath --relative-to=. target/clj-totp-cli-*-standalone.jar)
|
||||||
|
|
||||||
|
echo "Using uberjar $UBERJAR"
|
||||||
|
|
||||||
|
$JAVA_CMD -agentlib:native-image-agent=config-output-dir=target/native-image\
|
||||||
|
-jar $UBERJAR import "deleteme" "otpauth-migration://offline?data=CkkKEJ0M4MyHfITKCwCfqPIttjESFHJ1YmVuY2pAMThCMTY5RDVGRjAwGgRTTldMIAEoATACQhMzYjkxMDQxNzI3NzgzNDIzNDYyEAIYASAA"
|
||||||
|
|
||||||
|
java -jar $UBERJAR delete "deleteme"
|
||||||
|
|||||||
82
deps.edn
82
deps.edn
@@ -1,24 +1,74 @@
|
|||||||
{:paths ["src"]
|
{:paths ["src" "resources" "target/classes"]
|
||||||
:deps {org.clojure/clojure {:mvn/version "1.12.1"}
|
:deps {org.clojure/clojure {:mvn/version "1.12.1"}
|
||||||
io.github.clojure/tools.build {:mvn/version "0.10.10"}
|
;; Native image (GraalVM). Tutorial: https://shagunagrawal.me/posts/setup-clojure-with-graalvm-for-native-image/
|
||||||
mvxcvi/alphabase {:mvn/version "3.0.185"} ;; https://github.com/greglook/alphabase
|
com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}
|
||||||
cli-matic/cli-matic {:mvn/version "0.5.4"} ;; https://github.com/l3nz/cli-matic
|
;; Local subprojects
|
||||||
;; For SQLite
|
clj-totp/core {:local/root "projects/core"}
|
||||||
com.github.seancorfield/next.jdbc {:mvn/version "1.3.1048"}
|
clj-totp/cli {:local/root "projects/cli"}
|
||||||
org.xerial/sqlite-jdbc {:mvn/version "3.50.3.0"}
|
clj-totp/web {:local/root "projects/web"}
|
||||||
;; For Datomic local
|
clj-totp/gui {:local/root "projects/gui"}
|
||||||
com.datomic/local {:mvn/version "1.0.291"};; https://docs.datomic.com/datomic-local.html
|
}
|
||||||
;; Native image (GraalVM)
|
|
||||||
com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}};; Tutorial: https://shagunagrawal.me/posts/setup-clojure-with-graalvm-for-native-image/
|
|
||||||
|
|
||||||
:aliases {;; Execute the app
|
:aliases {;; Execute the app.
|
||||||
:run {:main-opts ["-m" "totp.app"]}
|
:run {:main-opts ["-m" "totp.app"]}
|
||||||
|
;:run {:exec-fn totp.app/-main}
|
||||||
|
|
||||||
|
;; Execute the app (prepared for more subprojects)
|
||||||
|
:run/cli {:main-opts ["-m" "totp.app"]}
|
||||||
|
|
||||||
|
:run/gui {:main-opts ["-m" "totp.gui"]}
|
||||||
|
|
||||||
;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
||||||
:test {:extra-paths ["test"] ;; https://cljdoc.org/d/uberdeps/uberdeps/1.4.0/doc/readme
|
;; Check test.edn for kaocha runner's config
|
||||||
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
|
:test {:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}
|
||||||
|
lambdaisland/kaocha-cloverage {:mvn/version "1.1.89"}}
|
||||||
:main-opts ["-m" "kaocha.runner"]}
|
:main-opts ["-m" "kaocha.runner"]}
|
||||||
|
|
||||||
;; Run with clj -T:build function-in-build
|
;; Run with clj -T:build function-in-build
|
||||||
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
:ns-default build}}}
|
;; Used by all compilations
|
||||||
|
:extra-deps {clj-totp/core {:local/root "projects/core"}}
|
||||||
|
:ns-default build}
|
||||||
|
|
||||||
|
;; Aliases for easy building
|
||||||
|
:build/core {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
|
:ns-default build
|
||||||
|
:exec-fn jar
|
||||||
|
:exec-args {:subproj "core"}}
|
||||||
|
|
||||||
|
:build/cli {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}
|
||||||
|
clj-totp/core {:local/root "projects/core"}}
|
||||||
|
:ns-default build
|
||||||
|
:exec-fn jar
|
||||||
|
:exec-args {:subproj "cli"}}
|
||||||
|
|
||||||
|
:build/web {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
|
:replace-deps {clj-totp/core {:local/root "projects/core"}}
|
||||||
|
:ns-default build
|
||||||
|
:exec-fn jar
|
||||||
|
:exec-args {:subproj "web"}}
|
||||||
|
|
||||||
|
:build/gui {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
|
:replace-deps {clj-totp/core {:local/root "projects/core"}}
|
||||||
|
:ns-default build
|
||||||
|
:exec-fn jar
|
||||||
|
:exec-args {:subproj "gui"}}
|
||||||
|
|
||||||
|
;; Build the three libraries
|
||||||
|
:build/all {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
|
:replace-deps {clj-totp/core {:local/root "projects/core"}}
|
||||||
|
:ns-default build
|
||||||
|
:exec-fn jar-all}
|
||||||
|
|
||||||
|
;; Build uber jar for CLI app
|
||||||
|
:uber/cli {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
|
:ns-default build
|
||||||
|
:exec-fn uber
|
||||||
|
:exec-args {:subproj "cli" :main-ns "totp.app"}}
|
||||||
|
|
||||||
|
;; Build uber jar for CLI app
|
||||||
|
:uber/gui {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||||
|
:ns-default build
|
||||||
|
:exec-fn uber
|
||||||
|
:exec-args {:subproj "gui" :main-ns "totp.app"}}}}
|
||||||
|
|
||||||
|
|||||||
33
native.cmd
Normal file
33
native.cmd
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
@echo off
|
||||||
|
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
REM YOUR LOCAL GRAAL VM INSTALLATION
|
||||||
|
set JAVA_HOME=D:\programas\graalvm-jdk-21.0.7+8.1
|
||||||
|
REM generated file
|
||||||
|
set BIN_FILE=totp
|
||||||
|
|
||||||
|
set DEST_DIR=C:\Users\rubencj\util
|
||||||
|
|
||||||
|
set PATH=%JAVA_HOME%\bin;%CLOJURE_HOME%;%PATH%
|
||||||
|
set NATIVE=%JAVA_HOME%\bin\native-image.cmd
|
||||||
|
|
||||||
|
|
||||||
|
echo Using GraalVM native compiler: %NATIVE%
|
||||||
|
|
||||||
|
echo Creating uberjar
|
||||||
|
clojure -T:build uber
|
||||||
|
|
||||||
|
set UBERJAR=
|
||||||
|
for /f "delims=" %%a in ('dir /b /s target\clj-totp-*-standalone.jar') do @set UBERJAR=%%a
|
||||||
|
echo Created uberjar: %UBERJAR%
|
||||||
|
|
||||||
|
echo "Creating native image"
|
||||||
|
cmd /c %NATIVE% -jar %UBERJAR% -o target\%BIN_FILE% -H:+ReportExceptionStackTraces --features=clj_easy.graal_build_time.InitClojureClasses --report-unsupported-elements-at-runtime --verbose --no-fallback -H:ReflectionConfigurationFiles=reflect_config.json -H:-CheckToolchain --initialize-at-build-time=org.fusesource.jansi.Ansi
|
||||||
|
|
||||||
|
|
||||||
|
echo Executable created: target\%BIN_FILE%.exe
|
||||||
|
|
||||||
|
copy target\%BIN_FILE%.exe %DEST_DIR%
|
||||||
|
echo Native image copied to %DEST_DIR%\%BIN_FILE%.exe
|
||||||
14
native.sh
14
native.sh
@@ -1,14 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
|
|
||||||
NATIVE=~/.sdkman/candidates/java/21.0.2-graalce/bin/native-image
|
|
||||||
UBERJAR=clj-topt-1.0.32-standalone.jar
|
|
||||||
BIN_FILE=totp
|
|
||||||
|
|
||||||
echo "Creating uberjar"
|
|
||||||
clojure -T:build uber
|
|
||||||
|
|
||||||
echo "Creating native image"
|
|
||||||
|
|
||||||
$NATIVE -jar target/$UBERJAR -o target/$BIN_FILE -H:+ReportExceptionStackTraces --features=clj_easy.graal_build_time.InitClojureClasses --report-unsupported-elements-at-runtime --verbose --no-fallback -H:ReflectionConfigurationFiles=./reflect_config.json
|
|
||||||
|
|
||||||
echo "Executable created on target/$BIN_FILE"
|
|
||||||
35
native_cli.sh
Executable file
35
native_cli.sh
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
NATIVE=~/.sdkman/candidates/java/21.0.2-graalce/bin/native-image
|
||||||
|
BIN_FILE=totp
|
||||||
|
|
||||||
|
echo "Creating uberjar"
|
||||||
|
#clojure -T:uber/cli
|
||||||
|
UBERJAR=$(realpath --relative-to=target target/clj-totp-cli-*-standalone.jar)
|
||||||
|
|
||||||
|
echo "Using uberjar $UBERJAR"
|
||||||
|
|
||||||
|
echo "Creating native image"
|
||||||
|
$NATIVE -jar target/$UBERJAR -o target/$BIN_FILE\
|
||||||
|
-H:+ReportExceptionStackTraces\
|
||||||
|
-H:ReflectionConfigurationFiles=./reflect_config.json\
|
||||||
|
--verbose --no-fallback\
|
||||||
|
--features=clj_easy.graal_build_time.InitClojureClasses\
|
||||||
|
--report-unsupported-elements-at-runtime\
|
||||||
|
--strict-image-heap\
|
||||||
|
-march=native\
|
||||||
|
-R:MaxHeapSize=10m\
|
||||||
|
--trace-object-instantiation=java.lang.Thread\
|
||||||
|
--initialize-at-build-time=org.fusesource.jansi.Ansi\
|
||||||
|
--initialize-at-build-time='org.fusesource.jansi.Ansi$Color'\
|
||||||
|
--initialize-at-build-time='org.fusesource.jansi.Ansi$Attribute'\
|
||||||
|
--initialize-at-build-time='org.fusesource.jansi.Ansi$1'\
|
||||||
|
--initialize-at-run-time=cljc_long.constants__init
|
||||||
|
|
||||||
|
|
||||||
|
echo "Executable created on target/$BIN_FILE"
|
||||||
|
cp target/$BIN_FILE ~/bin
|
||||||
|
echo "Copied to ~/bin/$BIN_FILE"
|
||||||
|
|
||||||
|
echo "Compress executable for distribution"
|
||||||
|
xz -fv target/$BIN_FILE
|
||||||
16
projects/cli/deps.edn
Executable file
16
projects/cli/deps.edn
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
{:paths ["src" "resources" "target/classes"]
|
||||||
|
:deps {;clj-totp/core {:local/root "../core"}
|
||||||
|
org.clojure/clojure {:mvn/version "1.12.1"}
|
||||||
|
cli-matic/cli-matic {:mvn/version "0.5.4"} ;; https://github.com/l3nz/cli-matic
|
||||||
|
;; Progress bar
|
||||||
|
com.github.pmonks/spinner {:mvn/version "2.0.284"}
|
||||||
|
com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}}
|
||||||
|
|
||||||
|
:aliases {;; Execute the app
|
||||||
|
;:run {:main-opts ["-m" "totp.app"]}
|
||||||
|
|
||||||
|
;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
||||||
|
:test {:extra-paths ["test"]
|
||||||
|
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
|
||||||
|
:main-opts ["-m" "kaocha.runner"]}}}
|
||||||
|
|
||||||
371
projects/cli/src/totp/app.clj
Normal file
371
projects/cli/src/totp/app.clj
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
(ns totp.app
|
||||||
|
#_{:clj-kondo/ignore [:refer-all]}
|
||||||
|
(:require [totp.core :refer :all]
|
||||||
|
[totp.data :refer :all]
|
||||||
|
[cli-matic.core :refer [run-cmd]]
|
||||||
|
[clojure.pprint :as pp]
|
||||||
|
[clojure.string :as str]
|
||||||
|
[progress.determinate :as pd])
|
||||||
|
(:import [java.util TimerTask Timer])
|
||||||
|
(:gen-class))
|
||||||
|
|
||||||
|
(def DEFAULT_BAR_STYLE :coloured-ascii-boxes)
|
||||||
|
|
||||||
|
(defn print-timer
|
||||||
|
([] (print-timer 1 30 DEFAULT_BAR_STYLE))
|
||||||
|
([bar-style] (print-timer 1 30 bar-style))
|
||||||
|
([start period bar-style]
|
||||||
|
(let [a (atom start)]
|
||||||
|
(pd/animate! a :opts {:total period
|
||||||
|
;:line 1
|
||||||
|
:label "Next TOTP: "
|
||||||
|
;:redraw-rate 60 ;; updates per second
|
||||||
|
:style (get pd/styles bar-style)}
|
||||||
|
;(println)
|
||||||
|
(run! (fn [_] (Thread/sleep 1000) (swap! a inc)) (range start (inc period)))
|
||||||
|
;(println)
|
||||||
|
))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- print-confinuous
|
||||||
|
([secret] (print-confinuous secret "sha1" 6 30 true DEFAULT_BAR_STYLE))
|
||||||
|
([secret algorithm digits period bar bar-style]
|
||||||
|
(let [step-millis (* 1000 period)
|
||||||
|
now (System/currentTimeMillis)
|
||||||
|
delay (int (- step-millis (rem now step-millis)))
|
||||||
|
delay-sec (int (/ delay 1000))
|
||||||
|
fn-show (fn [s] (println (format "%n[%d] %s%n"
|
||||||
|
(System/currentTimeMillis)
|
||||||
|
(get-otp s algorithm digits period))))
|
||||||
|
task (proxy [TimerTask] []
|
||||||
|
(run [] (println) (fn-show secret)))
|
||||||
|
task-bar (proxy [TimerTask] []
|
||||||
|
(run [] (print-timer bar-style)))
|
||||||
|
task-init (proxy [TimerTask] []
|
||||||
|
(run [] (print-timer (- period delay-sec) period bar-style)))]
|
||||||
|
(println "\n <Generating continuosly, press enter to stop>\n")
|
||||||
|
;; (println "Now:" now ", Delay:" delay ", Next execution: " (+ now delay))
|
||||||
|
(println "Refresing in" delay-sec "seconds")
|
||||||
|
(fn-show secret)
|
||||||
|
;; Now, start the tasks
|
||||||
|
(when bar
|
||||||
|
(. (new Timer) (schedule task-init 0))
|
||||||
|
(. (new Timer) (scheduleAtFixedRate task-bar delay step-millis)))
|
||||||
|
(. (new Timer) (scheduleAtFixedRate task delay step-millis)))
|
||||||
|
;; Waits for a key press
|
||||||
|
(read-line)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn cmd-generate
|
||||||
|
[& {:keys [secret continuous algorithm digits period bar bar-style]}]
|
||||||
|
;;(pp/pprint opts)
|
||||||
|
(if continuous
|
||||||
|
(print-confinuous secret algorithm digits period bar bar-style)
|
||||||
|
(println (get-otp secret algorithm digits period))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn- print-app
|
||||||
|
[app]
|
||||||
|
(let [{:keys [name secret algorithm digits period]
|
||||||
|
:or {algorithm "sha1"
|
||||||
|
digits 6
|
||||||
|
period 30}} app]
|
||||||
|
(println (format "[%d] %12s -> %s" (System/currentTimeMillis) name (get-otp secret algorithm digits period)))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- print-app-continuous
|
||||||
|
([period apps]
|
||||||
|
(let [step-millis (* 1000 period)
|
||||||
|
now (System/currentTimeMillis)
|
||||||
|
delay (int (- step-millis (rem now step-millis)))
|
||||||
|
delay-sec (int (/ delay 1000))
|
||||||
|
fn-show (fn [s]
|
||||||
|
(println "\n")
|
||||||
|
(dorun (map print-app s))
|
||||||
|
(println) ;; Separate each
|
||||||
|
)
|
||||||
|
task (proxy [TimerTask] []
|
||||||
|
(run [] (fn-show apps)))]
|
||||||
|
(println "\n <Generating continuosly, press enter to stop>\n")
|
||||||
|
;; (println "Now:" now ", Delay:" delay ", Next execution: " (+ now delay))
|
||||||
|
(println "Refresing in" delay-sec "seconds")
|
||||||
|
(fn-show apps)
|
||||||
|
;; Now, start the tasks
|
||||||
|
(. (new Timer) (scheduleAtFixedRate task delay step-millis)))
|
||||||
|
)) ;; Waits for a key press
|
||||||
|
|
||||||
|
|
||||||
|
(defn cmd-get-multi
|
||||||
|
[& {:keys [continuous bar bar-style _arguments]}]
|
||||||
|
;(pp/pprint opts)
|
||||||
|
(with-config
|
||||||
|
(let [apps (filter some? #_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(map #(get-app cfg %) _arguments))]
|
||||||
|
;(println "found apps: " apps)
|
||||||
|
(if continuous
|
||||||
|
(let [period 30
|
||||||
|
step-millis (* 1000 period)
|
||||||
|
now (System/currentTimeMillis)
|
||||||
|
delay (int (- step-millis (rem now step-millis)))
|
||||||
|
delay-sec (int (/ delay 1000))
|
||||||
|
task-bar (proxy [TimerTask] []
|
||||||
|
(run [] (print-timer bar-style)))
|
||||||
|
task-init (proxy [TimerTask] []
|
||||||
|
(run [] (print-timer (- period delay-sec) period bar-style)))]
|
||||||
|
(print-app-continuous period apps)
|
||||||
|
(when bar
|
||||||
|
(. (new Timer) (schedule task-init 0))
|
||||||
|
(. (new Timer) (scheduleAtFixedRate task-bar delay step-millis)))
|
||||||
|
(read-line))
|
||||||
|
(dorun (map #(print-app %) apps))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn cmd-config
|
||||||
|
[& {:keys [command]}]
|
||||||
|
;;(pp/pprint opts)
|
||||||
|
(case command
|
||||||
|
"info" (println "Configuration file:"
|
||||||
|
(if (exists-config)
|
||||||
|
cfg-file
|
||||||
|
(str "not found. Expected location: " cfg-file)))
|
||||||
|
"init" (if (exists-config)
|
||||||
|
(do
|
||||||
|
(println "Configuration already exists, this will delete it. Are you sure? [N/y]")
|
||||||
|
(case (read-line)
|
||||||
|
"y" (create-cfg-file)
|
||||||
|
"Y" (create-cfg-file)
|
||||||
|
(println "Cancelling operation.")))
|
||||||
|
(create-cfg-file))
|
||||||
|
"show" (do
|
||||||
|
(println "Config file:\n")
|
||||||
|
;(println (slurp cfg-file))
|
||||||
|
(pp/print-table (load-config)))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn cmd-list
|
||||||
|
[& {:keys [sorted mode]}]
|
||||||
|
(println "List mode:" mode)
|
||||||
|
(if (exists-config)
|
||||||
|
(case mode
|
||||||
|
"list" (let [apps-list (list-apps (load-config))
|
||||||
|
s-list (if sorted (sort apps-list) apps-list)]
|
||||||
|
(dorun (map #(println %) s-list)))
|
||||||
|
"table" (pp/print-table (load-config))
|
||||||
|
)
|
||||||
|
(println "Config file not found.")))
|
||||||
|
|
||||||
|
|
||||||
|
(defn cmd-add
|
||||||
|
[& {:keys [name secret user issuer algorithm digits period update] }]
|
||||||
|
;;(pp/pprint opts)
|
||||||
|
(when (not (exists-config))
|
||||||
|
(println "Config not found. Creating new config")
|
||||||
|
(create-cfg-file))
|
||||||
|
(let [cfg (load-config)]
|
||||||
|
(if (or update (nil? (get-app cfg name))) ;; get-app returns nil if app don't exists
|
||||||
|
(do
|
||||||
|
(store-config (add-app cfg name secret user issuer algorithm digits period))
|
||||||
|
(println "App" name "added or updated."))
|
||||||
|
(println "App" name "already exists.\nUse --update if you want to overwrite"))))
|
||||||
|
|
||||||
|
|
||||||
|
#_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(defn cmd-import
|
||||||
|
[& {:keys [name url update] }]
|
||||||
|
(with-config
|
||||||
|
(if (or update (nil? (get-app cfg name)))
|
||||||
|
(cond
|
||||||
|
(str/starts-with? url "otpauth-migration")
|
||||||
|
(do
|
||||||
|
(store-config (import-from-url-export cfg name url))
|
||||||
|
(println "Import successful"))
|
||||||
|
(str/starts-with? url "otpauth")
|
||||||
|
(do
|
||||||
|
(store-config (import-from-url-create cfg name url))
|
||||||
|
(println "Import successful"))
|
||||||
|
:else (println "URL type not supported"))
|
||||||
|
(println "App" name "already exists.\nUse --update if you want to overwrite"))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn cmd-delete
|
||||||
|
[& {:keys [name force]}]
|
||||||
|
;;(pp/pprint opts)
|
||||||
|
(if (exists-config)
|
||||||
|
(let [cfg (load-config)]
|
||||||
|
(when (or force (some? (get-app cfg name)))
|
||||||
|
(store-config (delete-app cfg name))))
|
||||||
|
(println "Config file not found.")))
|
||||||
|
|
||||||
|
|
||||||
|
(def cli-options
|
||||||
|
{:app {:command "totp"
|
||||||
|
:version "1.2"
|
||||||
|
:description ["Generate a TOTP"]}
|
||||||
|
|
||||||
|
:commands [;; Generate a TOTP with given params
|
||||||
|
{:command "generate"
|
||||||
|
:description "Generate one TOTP for a BASE32 secret, ignoring configured apps"
|
||||||
|
:examples ["Generate one TOTP for a provided BASE32 secret:"
|
||||||
|
" totp generate ABCD1234"
|
||||||
|
"Generate one TOTP and refresh it continuosly:"
|
||||||
|
" totp generate -c ABCD1234"]
|
||||||
|
:opts [{:option "secret" :short 0
|
||||||
|
:as "Secret encoded in BASE32"
|
||||||
|
:type :string
|
||||||
|
:default :present}
|
||||||
|
{:option "continuous" :short "c"
|
||||||
|
:as "Contiuous mode"
|
||||||
|
:type :with-flag
|
||||||
|
:default false}
|
||||||
|
{:option "algorithm" :short "a"
|
||||||
|
:as "Algorithm used for the key generation"
|
||||||
|
:type #{"sha1" "sha256" "sha512"}
|
||||||
|
:default "sha1"}
|
||||||
|
{:option "digits" :short "d"
|
||||||
|
:as "Number of digits for OTP. Usually 6 or 8"
|
||||||
|
:type :int
|
||||||
|
:default 6}
|
||||||
|
{:option "period" :short "p"
|
||||||
|
:as "Validity time in seconds"
|
||||||
|
:type :int
|
||||||
|
:default 30}
|
||||||
|
{:option "bar" :short "b"
|
||||||
|
:as "Show progress bar"
|
||||||
|
:type :with-flag
|
||||||
|
:default true}
|
||||||
|
{:option "bar-style" :short "s"
|
||||||
|
:as "Progress bar style"
|
||||||
|
:type #{:ascii-basic :ascii-boxes :coloured-ascii-boxes :emoji-circles :emoji-boxes}
|
||||||
|
:default :coloured-ascii-boxes}]
|
||||||
|
:runs cmd-generate}
|
||||||
|
;; Generate a TOTP for a configured app
|
||||||
|
{:command "get" :short "g"
|
||||||
|
:description "Generate one TOTP for a configured app"
|
||||||
|
:examples ["Generate one TOTP for a provided app:"
|
||||||
|
" totp get app1"
|
||||||
|
"Generate one TOTP and refresh it continuosly:"
|
||||||
|
" totp get -c app1"]
|
||||||
|
:opts [{:option "name" :short 0
|
||||||
|
:as "Name of the previous configured app"
|
||||||
|
:type :string
|
||||||
|
:default :present}
|
||||||
|
{:option "continuous" :short "c"
|
||||||
|
:as "Contiuous mode"
|
||||||
|
:type :with-flag
|
||||||
|
:default false}
|
||||||
|
{:option "bar" :short "b"
|
||||||
|
:as "Show progress bar"
|
||||||
|
:type :with-flag
|
||||||
|
:default true}
|
||||||
|
{:option "bar-style" :short "s"
|
||||||
|
:as "Progress bar style"
|
||||||
|
:type #{:ascii-basic :ascii-boxes :coloured-ascii-boxes :emoji-circles :emoji-boxes}
|
||||||
|
:default :coloured-ascii-boxes}]
|
||||||
|
:runs cmd-get-multi}
|
||||||
|
;; Check and init your config file
|
||||||
|
{:command "config" :short "c"
|
||||||
|
:description "Manage configuration"
|
||||||
|
:examples ["Show location for the configuration file:"
|
||||||
|
" totp config info"
|
||||||
|
"Recreate config (warning: it will delete the existing config file):"
|
||||||
|
" totp config init"
|
||||||
|
"Show configuration:"
|
||||||
|
" totp config show"]
|
||||||
|
:opts [{:option "command" :short 0
|
||||||
|
:as "Command to execute. See examples"
|
||||||
|
:type #{"info" "init" "show"}
|
||||||
|
:default :present}]
|
||||||
|
:runs cmd-config}
|
||||||
|
;; List available apps
|
||||||
|
{:command "list" :short "l"
|
||||||
|
:description "List existing apps"
|
||||||
|
:examples ["List apps:"
|
||||||
|
" totp list"
|
||||||
|
"List apps sorted by name:"
|
||||||
|
" totp list --sorted"]
|
||||||
|
:opts [{:option "sorted" :short "s"
|
||||||
|
:as "If provided, the list will be sorted by name"
|
||||||
|
:type :with-flag
|
||||||
|
:default false}
|
||||||
|
{:option "mode" :short "m"
|
||||||
|
:as "How to show the list of configured apps"
|
||||||
|
:type #{"list" "table"}
|
||||||
|
:default "list"}]
|
||||||
|
:runs cmd-list}
|
||||||
|
;; Adds a new app to the configuration
|
||||||
|
{:command "add" :short "a"
|
||||||
|
:description "Add a new application with an unique name"
|
||||||
|
:examples ["Add a new application named 'app1' with a BASE32 secred, with defaults:"
|
||||||
|
" totp add app1 \"MJXW42LBORXQ====\""
|
||||||
|
"Add an application, with all posible configuration params:"
|
||||||
|
" topt add app2 \"MJXW42LBORXQ====\" -u \"user1@server\" -i my_provider -a sha1 -d 6 -p 30 --update"]
|
||||||
|
:opts [{:option "name" :short 0
|
||||||
|
:as "Unique name (alias) for the application"
|
||||||
|
:type :string
|
||||||
|
:default :present}
|
||||||
|
{:option "secret" :short 1
|
||||||
|
:as "Secret encoded in BASE32"
|
||||||
|
:type :string
|
||||||
|
:default :present}
|
||||||
|
{:option "user" :short "u"
|
||||||
|
:as "Username in the format <user>@<server>"
|
||||||
|
:type :string}
|
||||||
|
{:option "issuer" :short "i"
|
||||||
|
:as "The issuer (provider) of the service"
|
||||||
|
:type :string}
|
||||||
|
{:option "algorithm" :short "a"
|
||||||
|
:as "Algorithm used for the key generation"
|
||||||
|
:type #{"sha1" "sha256" "sha512"}
|
||||||
|
:default "sha1"}
|
||||||
|
{:option "digits" :short "d"
|
||||||
|
:as "Number of digits for OTP. Usually 6 or 8"
|
||||||
|
:type :int
|
||||||
|
:default 6}
|
||||||
|
{:option "period" :short "p"
|
||||||
|
:as "Validity time in seconds"
|
||||||
|
:type :int
|
||||||
|
:default 30}
|
||||||
|
{:option "update"
|
||||||
|
:as "Update an app with the same name if exists"
|
||||||
|
:type :with-flag
|
||||||
|
:default false}]
|
||||||
|
:runs cmd-add}
|
||||||
|
;; Import from URL
|
||||||
|
{:command "import" :short "i"
|
||||||
|
:description "Import a TOTP config from a URL"
|
||||||
|
:examples ["Import from a QR for creation"
|
||||||
|
" totp import app1 \"otpauth://totp/<label>?issuer=<issuer>&secret=<base32 secret>\""
|
||||||
|
"Import from a QR for exportation"
|
||||||
|
" totp import app2 \"otpauth-migration://offline?data=<exported data>\""]
|
||||||
|
:opts [{:option "name" :short 0
|
||||||
|
:as "Unique name of the application"
|
||||||
|
:type :string
|
||||||
|
:default :present}
|
||||||
|
{:option "url" :short 1
|
||||||
|
:as "Imported URL"
|
||||||
|
:type :string
|
||||||
|
:default :present}
|
||||||
|
{:option "update"
|
||||||
|
:as "Update an app with the same name if exists"
|
||||||
|
:type :with-flag
|
||||||
|
:default false}]
|
||||||
|
:runs cmd-import}
|
||||||
|
;; Deletes an existing app from configuration
|
||||||
|
{:command "delete" :short "d"
|
||||||
|
:description "Removes an existing application by it's unique name"
|
||||||
|
:examples ["Remove the application name app1"
|
||||||
|
" totp remove app1"]
|
||||||
|
:opts [{:option "name" :short 0
|
||||||
|
:as "Unique name of the application"
|
||||||
|
:type :string
|
||||||
|
:default :present}
|
||||||
|
{:option "force" :short "f"
|
||||||
|
:as "Don't ask, just remove"
|
||||||
|
:type :with-flag
|
||||||
|
:default false}]
|
||||||
|
:runs cmd-delete}]})
|
||||||
|
|
||||||
|
|
||||||
|
(defn -main [& args]
|
||||||
|
(run-cmd args cli-options))
|
||||||
1
projects/cli/tests.edn
Normal file
1
projects/cli/tests.edn
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#kaocha/v1 {}
|
||||||
14
projects/core/deps.edn
Executable file
14
projects/core/deps.edn
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
{:paths ["src" "resources" "target/classes"]
|
||||||
|
:deps {org.clojure/clojure {:mvn/version "1.12.1"}
|
||||||
|
mvxcvi/alphabase {:mvn/version "3.0.185"} ;; https://github.com/greglook/alphabase
|
||||||
|
;; Protobuf for java
|
||||||
|
com.google.protobuf/protobuf-java {:mvn/version "3.25.8"}
|
||||||
|
;; Dynamic protobuf
|
||||||
|
com.github.s-expresso/clojobuf {:mvn/version "0.2.1"}
|
||||||
|
}
|
||||||
|
|
||||||
|
:aliases {;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
||||||
|
:test {:extra-paths ["test"]
|
||||||
|
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
|
||||||
|
:main-opts ["-m" "kaocha.runner"]}}}
|
||||||
|
|
||||||
8
projects/core/resources/clj-totp.sh
Executable file
8
projects/core/resources/clj-totp.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
JAVA_EXECUTABLE=java
|
||||||
|
UBER_JAR=$(realpath clj-totp-*-standalone.jar)
|
||||||
|
OPTS="-Xms256m -Xmx256m -client -Dclojure.spec.skip-macros=true"
|
||||||
|
|
||||||
|
|
||||||
|
$JAVA_EXECUTABLE $OPTS -jar $UBER_JAR $@
|
||||||
39
projects/core/resources/proto/otpauth-migration.proto
Normal file
39
projects/core/resources/proto/otpauth-migration.proto
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
message MigrationPayload {
|
||||||
|
enum Algorithm {
|
||||||
|
ALGORITHM_UNSPECIFIED = 0;
|
||||||
|
ALGORITHM_SHA1 = 1;
|
||||||
|
ALGORITHM_SHA256 = 2;
|
||||||
|
ALGORITHM_SHA512 = 3;
|
||||||
|
ALGORITHM_MD5 = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DigitCount {
|
||||||
|
DIGIT_COUNT_UNSPECIFIED = 0;
|
||||||
|
DIGIT_COUNT_SIX = 1;
|
||||||
|
DIGIT_COUNT_EIGHT = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OtpType {
|
||||||
|
OTP_TYPE_UNSPECIFIED = 0;
|
||||||
|
OTP_TYPE_HOTP = 1;
|
||||||
|
OTP_TYPE_TOTP = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OtpParameters {
|
||||||
|
bytes secret = 1;
|
||||||
|
string name = 2;
|
||||||
|
string issuer = 3;
|
||||||
|
Algorithm algorithm = 4;
|
||||||
|
DigitCount digits = 5;
|
||||||
|
OtpType type = 6;
|
||||||
|
int64 counter = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
repeated OtpParameters otp_parameters = 1;
|
||||||
|
int32 version = 2;
|
||||||
|
int32 batch_size = 3;
|
||||||
|
int32 batch_index = 4;
|
||||||
|
int32 batch_id = 5;
|
||||||
|
}
|
||||||
127
projects/core/src/totp/core.clj
Normal file
127
projects/core/src/totp/core.clj
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
(ns totp.core
|
||||||
|
(:require [alphabase.base32 :as b32]
|
||||||
|
[clojure.math :as m])
|
||||||
|
(:import (javax.crypto Mac)
|
||||||
|
(javax.crypto.spec SecretKeySpec)
|
||||||
|
(java.util Base64 Arrays)
|
||||||
|
(java.nio ByteBuffer)))
|
||||||
|
|
||||||
|
(def ^:private byte-array-type (type (.getBytes "")))
|
||||||
|
|
||||||
|
(defn timestamp->steps
|
||||||
|
"Converts from UNIX timestamp in milliseconds to a number os steps of 's' seconds of duration"
|
||||||
|
[time, step-size]
|
||||||
|
(if (or (nil? time) (nil? step-size) (zero? step-size))
|
||||||
|
0
|
||||||
|
(int (quot time (* 1000 step-size)))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn bytes-array?
|
||||||
|
"Return true if x is a byte[]"
|
||||||
|
[x]
|
||||||
|
(= byte-array-type (type x)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-alg
|
||||||
|
[alg]
|
||||||
|
(case alg
|
||||||
|
"sha1" "HmacSHA1"
|
||||||
|
"sha256" "HmacSHA256"
|
||||||
|
"sha512" "HmacSHA512"
|
||||||
|
""))
|
||||||
|
|
||||||
|
|
||||||
|
(defmulti hmac
|
||||||
|
"Generates an HMAC. Algorithms supported: sha1, sha256, sha512.
|
||||||
|
The key and the message can be (both) string or array of bytes, nil otherwise"
|
||||||
|
(fn [algorithm key message]
|
||||||
|
(cond
|
||||||
|
(and (string? key) (string? message) (some? (get-alg algorithm))) :string
|
||||||
|
(and (bytes-array? key) (bytes-array? message) (some? (get-alg algorithm))) :byte
|
||||||
|
:else :nil)))
|
||||||
|
|
||||||
|
;; By default
|
||||||
|
(defmethod hmac :nil [_ _ _]
|
||||||
|
nil)
|
||||||
|
|
||||||
|
;; When key and message are strings
|
||||||
|
(defmethod hmac :string [algorithm key message]
|
||||||
|
(if (or (empty? key) (empty? message))
|
||||||
|
""
|
||||||
|
(let [mac (doto (Mac/getInstance (get-alg algorithm)) (.init (SecretKeySpec. (.getBytes key) (get-alg algorithm))))
|
||||||
|
hmac-bytes (.doFinal mac (.getBytes message))]
|
||||||
|
;; Return the Base64 encoded HMAC
|
||||||
|
(.encodeToString (Base64/getEncoder) hmac-bytes))))
|
||||||
|
|
||||||
|
;; When key and message are arrays of bytes
|
||||||
|
(defmethod hmac :byte [algorithm key message]
|
||||||
|
(if (nil? message)
|
||||||
|
(bytes (byte-array 0))
|
||||||
|
(let [mac (doto (Mac/getInstance (get-alg algorithm)) (.init (SecretKeySpec. key (get-alg algorithm))))
|
||||||
|
hmac-bytes (.doFinal mac message)]
|
||||||
|
;; Return the Base64 encoded HMAC
|
||||||
|
(Base64/getEncoder) hmac-bytes)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn long->bytes
|
||||||
|
"Converts a long to an array of 8 bytes"
|
||||||
|
[l]
|
||||||
|
;;Java equivalent: ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(someLong).array();
|
||||||
|
(when (integer? l)
|
||||||
|
(-> (ByteBuffer/allocate (/ Long/SIZE Byte/SIZE))
|
||||||
|
(.putLong l)
|
||||||
|
(.array))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn bytes->int
|
||||||
|
"Converts an array of 4 bytes to an integer"
|
||||||
|
[bytes]
|
||||||
|
;;Java equivalent: ByteBuffer.wrap(data).getInt()
|
||||||
|
(when (some? bytes)
|
||||||
|
(.getInt (ByteBuffer/wrap bytes))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-otp
|
||||||
|
"Generate an OTP with the given secret (in base32) for the specified timestep"
|
||||||
|
([secret algorithm digits period] ;;algorithm digits period
|
||||||
|
(when (and secret period)
|
||||||
|
(let [step (timestamp->steps (System/currentTimeMillis) period)
|
||||||
|
k (b32/decode secret)
|
||||||
|
c (long->bytes step)
|
||||||
|
hs (hmac algorithm k c)
|
||||||
|
offset (bit-and (get hs (dec (count hs))) 0x0f) ;; int offset = hs[hs.length-1] & 0xf;
|
||||||
|
chunk (Arrays/copyOfRange hs offset (+ offset 4)) ;(take 4 (drop offset hs)) ;; byte[] chunk = Arrays.copyOfRange(hs, offset, offset+4)
|
||||||
|
]
|
||||||
|
(format (str "%0" digits "d")
|
||||||
|
(-> chunk
|
||||||
|
(bytes->int)
|
||||||
|
(bit-and 0x7fffffff)
|
||||||
|
(rem (int (m/pow 10 digits))))))))
|
||||||
|
([secret]
|
||||||
|
(get-otp secret "sha1" 6 30)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn calculate-offset-millis
|
||||||
|
[period]
|
||||||
|
(let [step-millis (* 1000 period)
|
||||||
|
now (System/currentTimeMillis)]
|
||||||
|
(int (rem now step-millis))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn calculate-delay-millis
|
||||||
|
[period]
|
||||||
|
(let [step-millis (* 1000 period)]
|
||||||
|
(- step-millis (calculate-offset-millis period))))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(let [now (System/currentTimeMillis)
|
||||||
|
off (calculate-offset-millis 30)
|
||||||
|
delay (calculate-delay-millis 30)
|
||||||
|
]
|
||||||
|
(println "Now: " (int (/ now 1000)) "secs" now "millis")
|
||||||
|
(println "Offset:" (int (/ off 1000)) "secs" off "millis")
|
||||||
|
(println "Delay: " (int (/ delay 1000)) "secs" delay "millis")
|
||||||
|
(println "Total: " (+ (int (/ off 1000)) (int (/ delay 1000))) "secs" (+ delay off) "millis")
|
||||||
|
)
|
||||||
|
)
|
||||||
249
projects/core/src/totp/data.clj
Normal file
249
projects/core/src/totp/data.clj
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
(ns totp.data
|
||||||
|
(:require [totp.otp-proto :as proto]
|
||||||
|
[clojure.edn :as e]
|
||||||
|
[clojure.string :as str]
|
||||||
|
[clojure.java.io :as io]
|
||||||
|
[clojure.pprint :as pp]
|
||||||
|
[alphabase.base64 :as b64]
|
||||||
|
[alphabase.base32 :as b32]))
|
||||||
|
|
||||||
|
(defn join-path
|
||||||
|
"Joins several subpaths using system's path separator (/ un *NIX and \\ in windows)"
|
||||||
|
[& col]
|
||||||
|
(str/join java.io.File/separator col))
|
||||||
|
|
||||||
|
|
||||||
|
(def cfg-path (join-path (System/getProperty "user.home") ".config" "totp"))
|
||||||
|
|
||||||
|
(def cfg-file (join-path cfg-path "apps.edn"))
|
||||||
|
|
||||||
|
(def cfg-header ";; clj-totp configuration file
|
||||||
|
;; This file contents a list of maps with :name and :secret entries
|
||||||
|
;; Secrets must be encoded in BASE32
|
||||||
|
|
||||||
|
")
|
||||||
|
|
||||||
|
|
||||||
|
(defn exists-config
|
||||||
|
"Checks if the config file exists"
|
||||||
|
[]
|
||||||
|
(.exists (io/file cfg-file)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn create-cfg-file
|
||||||
|
"Creates the config file"
|
||||||
|
[]
|
||||||
|
(println "Creating " cfg-file)
|
||||||
|
(io/delete-file cfg-file true)
|
||||||
|
(io/make-parents cfg-file)
|
||||||
|
(spit cfg-file cfg-header)
|
||||||
|
true)
|
||||||
|
|
||||||
|
|
||||||
|
(defn create-cfg?
|
||||||
|
"Create configuration file if not exists. Overridable with allways = true"
|
||||||
|
([] (create-cfg? false))
|
||||||
|
([allways]
|
||||||
|
(if (or allways (not (exists-config)))
|
||||||
|
(create-cfg-file)
|
||||||
|
false)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(exists-config)
|
||||||
|
(create-cfg?))
|
||||||
|
|
||||||
|
|
||||||
|
(defn load-config
|
||||||
|
"Loads configuration from file"
|
||||||
|
[]
|
||||||
|
(e/read-string (slurp cfg-file)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn store-config
|
||||||
|
"Store configuration to file"
|
||||||
|
[cfg]
|
||||||
|
(when cfg
|
||||||
|
(spit cfg-file (str cfg-header (with-out-str
|
||||||
|
(binding [pp/*print-right-margin* 50]
|
||||||
|
(pp/pprint cfg)))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn delete-app
|
||||||
|
[cfg name]
|
||||||
|
(filter #(not= name (:name %)) cfg))
|
||||||
|
|
||||||
|
|
||||||
|
(defn create-app
|
||||||
|
([name secret] (create-app name secret nil nil "sha1" 6 30))
|
||||||
|
([name secret user issuer] (create-app name secret user issuer "sha1" 6 30))
|
||||||
|
([name secret user issuer algorithm digits period]
|
||||||
|
{:name name :secret secret :user user :issuer issuer :algorithm algorithm :digits digits :period period}))
|
||||||
|
|
||||||
|
|
||||||
|
(defn add-app
|
||||||
|
([cfg app-map] (apply add-app (cons cfg (vals app-map))))
|
||||||
|
([cfg name secret] (add-app cfg name secret nil nil "sha1" 6 30))
|
||||||
|
([cfg name secret user issuer algorithm digits period]
|
||||||
|
(conj (delete-app cfg name) (create-app name secret user issuer algorithm digits period))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn list-apps
|
||||||
|
[cfg]
|
||||||
|
(->> cfg
|
||||||
|
(filter #(contains? % :name))
|
||||||
|
(map :name)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(list-apps (load-config))
|
||||||
|
(with-config #_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(list-apps cfg))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-app
|
||||||
|
[cfg name]
|
||||||
|
(let [app (first (filter #(= name (:name %)) cfg))]
|
||||||
|
(if app
|
||||||
|
app
|
||||||
|
(println "App" name "not found"))))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(exists-config)
|
||||||
|
(create-cfg?)
|
||||||
|
(load-config)
|
||||||
|
|
||||||
|
(get-app [{:name "abc" :secret "def"} {:name "my-app" :secret "abc123"} {:name "another app" :secret "ABCDEF1234"}] "my-app2")
|
||||||
|
|
||||||
|
(with-out-str
|
||||||
|
(binding [pp/*print-right-margin* 50]
|
||||||
|
(pp/pprint [{:name "abc" :secret "def"} {:name "my-app" :secret "abc123"}])))
|
||||||
|
|
||||||
|
(store-config [{:name "abc" :secret "def"} {:name "my-app" :secret "abc123"} {:name "another app" :secret "ABCDEF1234"}])
|
||||||
|
|
||||||
|
(-> nil
|
||||||
|
(add-app "app1" "abc123abc123")
|
||||||
|
(add-app "app2" "abc123abc123")
|
||||||
|
(add-app "app1" "123456789012")
|
||||||
|
(store-config))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defmacro with-config
|
||||||
|
"Loads config file and stores it in a cfg binding.
|
||||||
|
You can use the cfg var the inner code.
|
||||||
|
|
||||||
|
Next example will print config data:
|
||||||
|
(with-config (println cfg))
|
||||||
|
|
||||||
|
Be cafefull: dont use a binding called cfg in any module.
|
||||||
|
"
|
||||||
|
[form]
|
||||||
|
(let [cfg (symbol "cfg")] ;; This symbol will prevent error with qualified in the inner let
|
||||||
|
`(if (exists-config)
|
||||||
|
(let [~cfg (load-config)] ;; This is the problematic let binding. See: https://stackoverflow.com/a/15122414
|
||||||
|
(if (some? ~cfg)
|
||||||
|
(do ~form)))
|
||||||
|
(println "Config file not found"))))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
#_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(with-config (first cfg))
|
||||||
|
|
||||||
|
(macroexpand-1 '(with-config (first cfg)))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn url-create->app
|
||||||
|
"Import data from url using the protocol otpauth://
|
||||||
|
|
||||||
|
Example: otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example
|
||||||
|
|
||||||
|
https://github.com/google/google-authenticator/wiki/Key-Uri-Format"
|
||||||
|
[name url]
|
||||||
|
(when (str/starts-with? url "otpauth://")
|
||||||
|
(let [parts (str/split url #"\?")
|
||||||
|
meta-parts (str/split (first parts) #"/" -1)
|
||||||
|
data-parts (str/split (second parts) #"&" -1)
|
||||||
|
otp-type (nth meta-parts 2)]
|
||||||
|
(if (not= "totp" otp-type) ;; Only totp is supported
|
||||||
|
(println "Invalid protocol OTP type:" otp-type)
|
||||||
|
(let [user-data (str/split (nth meta-parts 3) #":" -1)
|
||||||
|
issuer (first user-data)
|
||||||
|
user (second user-data)
|
||||||
|
;data-map (apply hash-map (flatten (map #(str/split % #"=") data-parts)))
|
||||||
|
data-map (reduce (fn [acc v] ;; From array to map
|
||||||
|
(let [[k v] (str/split v #"=")]
|
||||||
|
(assoc acc (keyword k) v)))
|
||||||
|
{} data-parts)
|
||||||
|
secret (:secret data-map)
|
||||||
|
;;issuer2 (:issuer data-map)
|
||||||
|
]
|
||||||
|
(create-app name secret user issuer))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(url-create->app "app1" "otpauth://totp/Reddit:errepunto?issuer=Reddit&secret=3RR2")
|
||||||
|
|
||||||
|
(url-create->app "app2" "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example")
|
||||||
|
|
||||||
|
(add-app [{:a 1 :b 2}] (url-create->app "app2" "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"))
|
||||||
|
)
|
||||||
|
|
||||||
|
(defn import-from-url-create
|
||||||
|
[cfg name url]
|
||||||
|
(add-app cfg (url-create->app name url)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
#_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(with-config (import-from-url-create cfg "app1" "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn url-export->app
|
||||||
|
[name url]
|
||||||
|
(when (some? url)
|
||||||
|
(let [b64-data (second (str/split url #"=" -1))
|
||||||
|
data-b (b64/decode b64-data)
|
||||||
|
payload (proto/parse-data data-b)
|
||||||
|
;{:keys [name secret name issuer digits algorithm type]} payload
|
||||||
|
secret (:secret payload)
|
||||||
|
user (:name payload)
|
||||||
|
issuer (:issuer payload)
|
||||||
|
algorithm (:algorithm payload)
|
||||||
|
digits (:digits payload)
|
||||||
|
valid-type (= 2 (:type payload))
|
||||||
|
]
|
||||||
|
;(println "name:" name "user:" user "issuer:" issuer "digits:" digits "algorithm:" algorithm "valid type?" valid-type)
|
||||||
|
(if valid-type
|
||||||
|
(create-app name secret user issuer algorithm digits 30)
|
||||||
|
(println "Invalid OTP type" (:type payload)))
|
||||||
|
)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(url-export->app "test"
|
||||||
|
"otpauth-migration://offline?data=CkkKEJ0M4MyHfITKCwCfqPIttjESFHJ1YmVuY2pAMThCMTY5RDVGRjAwGgRTTldMIAEoATACQhMzYjkxMDQxNzI3NzgzNDIzNDYyEAIYASAA"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn import-from-url-export
|
||||||
|
"Import data from url using the protocol otpauth-migration://
|
||||||
|
|
||||||
|
https://alexbakker.me/post/parsing-google-auth-export-qr-code.html"
|
||||||
|
[cfg name url]
|
||||||
|
(add-app cfg (url-export->app name url)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
#_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(with-config (import-from-url-export
|
||||||
|
cfg
|
||||||
|
"app2"
|
||||||
|
"otpauth-migration://offline?data=CkkKEJ0M4MyHfITKCwCfqPIttjESFHJ1YmVuY2pAMThCMTY5RDVGRjAwGgRTTldMIAEoATACQhMzYjkxMDQxNzI3NzgzNDIzNDYyEAIYASAA")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
385
projects/core/src/totp/otp_import.clj
Normal file
385
projects/core/src/totp/otp_import.clj
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
(ns totp.otp-import
|
||||||
|
(:require [alphabase.bytes :as b]
|
||||||
|
[alphabase.base16 :as hex]
|
||||||
|
[alphabase.base64 :as b64]
|
||||||
|
[alphabase.base2 :as b2]
|
||||||
|
[clojure.math :as m]
|
||||||
|
[clojure.string :as s]))
|
||||||
|
|
||||||
|
;; Original description of the export protocol uses Google's Protocol Buffers
|
||||||
|
;; https://protobuf.dev/
|
||||||
|
|
||||||
|
(def WIRE_TYPES {:varint 0 ;; Integeres of variable length encoding
|
||||||
|
:i64 1 ;; Fixed 64 bits numbers (integer or decimal)
|
||||||
|
:len 2 ;; Block of bytes with a predefined length
|
||||||
|
:sgroup 3 ;; Group end (deprecated)
|
||||||
|
:egroup 4 ;; Group start (deprecated)
|
||||||
|
:i32 5 ;; Fixed 32 bits number (integer or decimal)
|
||||||
|
})
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(let [encoded "CkkKEJ0M4MyHfITKCwCfqPIttjESFHJ1YmVuY2pAMThCMTY5RDVGRjAwGgRTTldMIAEoATACQhMzYjkxMDQxNzI3NzgzNDIzNDYyEAIYASAA"
|
||||||
|
decoded (b64/decode encoded)]
|
||||||
|
(print (hex/encode decoded))
|
||||||
|
(print (b/to-string decoded))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn to-fancy-bin
|
||||||
|
"Return a string with bits from number in a fancy manner"
|
||||||
|
[x]
|
||||||
|
(s/join " "
|
||||||
|
(map s/join
|
||||||
|
(partition 8 (b2/encode (byte-array (map #(b/to-byte %) x)))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn to-fancy-hex
|
||||||
|
"Return a string with hex values from number in a fancy manner"
|
||||||
|
([x]
|
||||||
|
(to-fancy-hex x 2))
|
||||||
|
([x group-size]
|
||||||
|
(s/join " "
|
||||||
|
(map s/join
|
||||||
|
(partition group-size (hex/encode (byte-array (map #(b/to-byte %) x))))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn len-bits
|
||||||
|
"How may blocks of n bits are needed to encode this number?
|
||||||
|
|
||||||
|
We use the following formula, for calculating the number of digits required
|
||||||
|
to encode the number n in the base b:
|
||||||
|
|
||||||
|
digits = ceil ( log_n ( x + 1 ) )
|
||||||
|
"
|
||||||
|
[n x]
|
||||||
|
(case x
|
||||||
|
nil 0 ;; nill is encoded with zero bytes
|
||||||
|
0 1 ;; One block to zero
|
||||||
|
9223372036854775807 (len-bits n (dec x)) ;; Beware the overflow!! it's best to lose some precision
|
||||||
|
(when (and (>= x 0) (some? n) (> n 0))
|
||||||
|
(long (m/ceil (/ (m/log (inc x)) (m/log (m/pow 2 n))))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(len-bits 8 513)
|
||||||
|
(len-bits 8 Long/MAX_VALUE)
|
||||||
|
(len-bits 8 (dec Long/MAX_VALUE)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn len-bytes
|
||||||
|
"How may bytes are needed to encode this number?"
|
||||||
|
[x]
|
||||||
|
(len-bits 8 x))
|
||||||
|
|
||||||
|
|
||||||
|
(defn integer>bytes
|
||||||
|
"Converts an integer to a byte array"
|
||||||
|
[x]
|
||||||
|
(when x (let [len (len-bytes x)
|
||||||
|
hex-len (* 2 len)]
|
||||||
|
(hex/decode (String/format (str "%0" hex-len "x") (into-array [x]))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(integer>bytes 513)
|
||||||
|
10r3
|
||||||
|
3r10)
|
||||||
|
|
||||||
|
|
||||||
|
(defn euclidean-quot
|
||||||
|
"Euclidean division for integers
|
||||||
|
|
||||||
|
More info: https://en.wikipedia.org/wiki/Euclidean_division
|
||||||
|
"
|
||||||
|
[a b]
|
||||||
|
(when (zero? b)
|
||||||
|
(throw (IllegalArgumentException. "You can't divide by zero!")))
|
||||||
|
(let [b-abs (Math/abs b)
|
||||||
|
r (mod a b-abs)] ;; 0 <= r < |b|
|
||||||
|
(quot (- a r) b) ;; adjusts quotient with the positive remainder
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
(defn euclidean-rem
|
||||||
|
"Modulus for euclidean division for integers
|
||||||
|
|
||||||
|
More info: https://en.wikipedia.org/wiki/Euclidean_division
|
||||||
|
"
|
||||||
|
[a b]
|
||||||
|
(when (zero? b)
|
||||||
|
(throw (IllegalArgumentException. "You can't divide by zero!")))
|
||||||
|
(let [b-abs (Math/abs b)]
|
||||||
|
(mod a b-abs);; 0 <= r < |b|
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
(defn euclid-div
|
||||||
|
"Euclidean division. Returns quotient and remainder as a map. The remainder is
|
||||||
|
allways a positive number.
|
||||||
|
|
||||||
|
More info: https://en.wikipedia.org/wiki/Euclidean_division"
|
||||||
|
[a b]
|
||||||
|
(when (zero? b)
|
||||||
|
(throw (IllegalArgumentException. "You can't divide by zero!")))
|
||||||
|
(let [b-abs (Math/abs b)
|
||||||
|
r (mod a b-abs) ;; 0 <= r < |b|
|
||||||
|
q (quot (- a r) b)] ;; adjusts quotient with the positive remainder
|
||||||
|
{:q q :r r}))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(euclid-div 7 3)
|
||||||
|
(euclid-div -7 3)
|
||||||
|
(euclid-div 7 -3)
|
||||||
|
(euclid-div -7 -3))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn decimal->base
|
||||||
|
"Converts a decimal number to an arbitrary base. Each digit is encoded as an
|
||||||
|
integer with value between 0 and base-1.
|
||||||
|
|
||||||
|
For example, 10 in base 4 will be encoded as:
|
||||||
|
[2 0 2]
|
||||||
|
, 127 in base 126 will be:
|
||||||
|
[1 1]
|
||||||
|
and so on.
|
||||||
|
|
||||||
|
We will use the sucessive division method: do a integer division of the number
|
||||||
|
to the base until the quotient is zero, and take the reminders backwards. For
|
||||||
|
example, if we want to converto 127 to base 5:
|
||||||
|
|
||||||
|
127 / 5 = 25 rem 2
|
||||||
|
25 / 5 = 5 rem 0
|
||||||
|
5 / 5 = 1 rem 0
|
||||||
|
1 / 5 = 0 rem 1
|
||||||
|
|
||||||
|
so, 127 in base 5 is 1002.
|
||||||
|
|
||||||
|
The maximum digits this method can return is 65. Max size for a long is base 2
|
||||||
|
is 64, so if you obtain a result with 65 digits is very probable that the
|
||||||
|
conversion has failed.
|
||||||
|
|
||||||
|
If the number is negative and the base is possitive, we return the conversion
|
||||||
|
of the absolute value. You must take care of the sign in your implementation.
|
||||||
|
|
||||||
|
There are negative bases as -2 that can encode possitive and negative numbers.
|
||||||
|
"
|
||||||
|
([base n]
|
||||||
|
(decimal->base base n true))
|
||||||
|
([base n reverse?]
|
||||||
|
(if (or (nil? n) (nil? base) (zero? n) (zero? base))
|
||||||
|
[0] ;; Allways [0] for base zero or number zero
|
||||||
|
(loop [acc []
|
||||||
|
x (if (and (< n 0) (> base 0)) (abs n) n)] ;; If n < 0 and b > 0 => |n|, else n
|
||||||
|
(let [q (euclidean-quot x base)
|
||||||
|
r (euclidean-rem x base)]
|
||||||
|
;;(printf "Acc: %s Calculating: %s/%s -> %s rem %s%n" acc x base q r)
|
||||||
|
(if (or (zero? x) (> (count acc) (inc Long/SIZE))) ;; max digits is 65
|
||||||
|
(vec (if reverse? (reverse acc) acc)) ;; When x is zero, we have finished
|
||||||
|
(recur
|
||||||
|
(conj acc r) ;; Accumulate the remainder
|
||||||
|
q ;; Pass the quotient to the next step
|
||||||
|
)))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(decimal->base 2 8)
|
||||||
|
2r1000
|
||||||
|
(decimal->base 2 3)
|
||||||
|
|
||||||
|
(letfn [(step [x b name q r]
|
||||||
|
(printf "%s -> %d/%d = %d rem %d%n" name x b q r))
|
||||||
|
(examples [x b exp-q exp-r]
|
||||||
|
(step x b "EXPECTED " exp-q exp-r)
|
||||||
|
(step x b "IEEE " (int (m/IEEE-remainder x b)) (rem x b))
|
||||||
|
(step x b "With rem " (quot x b) (rem x b))
|
||||||
|
(step x b "With mod " (quot x b) (mod x b))
|
||||||
|
(step x b "floor " (m/floor-div x b) (m/floor-mod x b))
|
||||||
|
(step x b "Manual 1 " (int (clojure.math/round (double (/ x b)))) (rem x b))
|
||||||
|
(step x b "Manual 2 " (int (clojure.math/rint (double (/ x b)))) (rem x b))
|
||||||
|
(step x b "Unchecked " (unchecked-divide-int x b) (unchecked-remainder-int x b))
|
||||||
|
(step x b "Euclidean " (euclidean-quot x b) (euclidean-rem x b)))]
|
||||||
|
(examples -3 -2 2 1)
|
||||||
|
(println)
|
||||||
|
(examples 2 -2 -1 0)
|
||||||
|
(println)
|
||||||
|
(examples -1 -2 1 1)
|
||||||
|
(println)
|
||||||
|
(examples 1 -2 0 1)
|
||||||
|
(println)
|
||||||
|
(println)
|
||||||
|
(examples 7 3 2 1)
|
||||||
|
(println)
|
||||||
|
(examples -7 3 -3 2)
|
||||||
|
(println)
|
||||||
|
(examples 7 -3 -2 1)
|
||||||
|
(println)
|
||||||
|
(examples -7 -3 3 2)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn base->decimal
|
||||||
|
"Converts from an array with values in an arbitrary base into decimal values"
|
||||||
|
[base n]
|
||||||
|
(if (or (nil? n) (nil? base) (zero? (count n)))
|
||||||
|
0
|
||||||
|
(long (reduce-kv
|
||||||
|
(fn [acc k v]
|
||||||
|
(+ acc (* v (m/pow base k))))
|
||||||
|
0 (vec (reverse n))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(reduce-kv #(+ %1 (* %3 (m/pow 2 %2))) 0 [1 0 0 0]) ;; backwards!
|
||||||
|
(base->decimal 2 [1 0 0 0]))
|
||||||
|
|
||||||
|
|
||||||
|
(defn int->varint
|
||||||
|
"Converts a integer value to a varint, that is encoded in 7 bits, where the first
|
||||||
|
bit is used to indicate if there are more bytes.
|
||||||
|
|
||||||
|
For example, the number 255 is usually encoded in a byte as follows:
|
||||||
|
11111111
|
||||||
|
but in a varint it will turn to:
|
||||||
|
10000001 01111111
|
||||||
|
|
||||||
|
First byte has the MSB set to 1, because there is another byte after it. The
|
||||||
|
second byte is the last one, so the MSB is set to 0.
|
||||||
|
|
||||||
|
The result is a byte array.
|
||||||
|
"
|
||||||
|
[x]
|
||||||
|
(let [b128 (decimal->base 128 x false)]
|
||||||
|
(byte-array (conj
|
||||||
|
(vec (map #(bit-or 2r10000000 %) (butlast b128)))
|
||||||
|
(bit-and 2r01111111 (peek b128))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn varint->int
|
||||||
|
"Converts a varint to an integer. Each byte in varint uses the MSB as a continuation
|
||||||
|
bit: if it's value is 1, there are more bites, if it's 0 it's the last block.
|
||||||
|
|
||||||
|
For example, this varint with two bytes:
|
||||||
|
10000001 01111111
|
||||||
|
|
||||||
|
first byte's MSB has value 1, so there is another byte. The second one has a zero
|
||||||
|
in the MSB, so it's the last block. To calculate the final value, you must ignore
|
||||||
|
the MSB and concatenate both bytes:
|
||||||
|
110010110 00000001 -> original value
|
||||||
|
0010110 0000001 -> delete MSB
|
||||||
|
0000001 0010110 -> reverse bytes (little-endian to big endian)
|
||||||
|
00000010010110 -> concatenate
|
||||||
|
00000000 10010110 -> fill the bytes
|
||||||
|
"
|
||||||
|
[x]
|
||||||
|
(base->decimal 128 (reverse (map #(bit-and 2r01111111 %) x))))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; 150
|
||||||
|
(to-fancy-bin [-106, 1])
|
||||||
|
(to-fancy-bin (map #(bit-and 2r01111111 %) [-106, 1]))
|
||||||
|
(to-fancy-bin (reverse (map #(bit-and 2r01111111 %) [-106, 1])))
|
||||||
|
(varint->int [-106, 1])
|
||||||
|
|
||||||
|
(varint->int [-106, 1])
|
||||||
|
(varint->int [-105, 1])
|
||||||
|
|
||||||
|
;; Long/MAX_VALUE
|
||||||
|
(to-fancy-bin (map #(bit-and 2r01111111 %) [-1, -1, -1, -1, -1, -1, -1, -1, 128]))
|
||||||
|
(varint->int [-2, -1, -1, -1, -1, -1, -1, -1, 127]))
|
||||||
|
|
||||||
|
|
||||||
|
(defn more-blocks?
|
||||||
|
"True if the MSB bit is 1"
|
||||||
|
[b]
|
||||||
|
(when b
|
||||||
|
(< 0 (bit-and 2r10000000 b))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(bit-and 2r10000000 2r10000001)
|
||||||
|
(bit-and 2r10000000 2r00000001)
|
||||||
|
( more-blocks? 2r10000001)
|
||||||
|
(more-blocks? 2r00000001)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defn extract-varint-blocks
|
||||||
|
"Group varints in a byte array"
|
||||||
|
[bytes]
|
||||||
|
(loop [acc [] ;; Groups of varints
|
||||||
|
group [] ;; Current varint
|
||||||
|
r bytes] ;; Current tested byte
|
||||||
|
(if (empty? r) ;; Final condition: no more bytes to test
|
||||||
|
(if ((complement empty?) group)
|
||||||
|
(conj acc group) ;; Return acc with the last group added if not empty
|
||||||
|
acc) ;; if empty, return acc
|
||||||
|
(if (more-blocks? (first r))
|
||||||
|
;; If more blocks remains:
|
||||||
|
(recur acc
|
||||||
|
(conj group (first r)) ;; add current byte to current group
|
||||||
|
(next r))
|
||||||
|
;; If it's last in varint:
|
||||||
|
(recur (->> (first r)
|
||||||
|
(conj group) ;; add byte to group
|
||||||
|
(conj acc)) ;; add group to accumulator
|
||||||
|
[] ;; and start a new empty group
|
||||||
|
(next r))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(extract-varint-blocks [2r11111111 2r01111111, 2r10000001 2r10101001 2r00000001, 2r00000111])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defn extract-varints
|
||||||
|
"Group varints in an byte array and convert them to decimal"
|
||||||
|
[bytes]
|
||||||
|
(map varint->int (extract-varint-blocks bytes)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(extract-varints [2r11111111 2r01111111, 2r10000001 2r10101001 2r00000001, 2r00000111])
|
||||||
|
|
||||||
|
(varint->int [2r11111111 2r01111111])
|
||||||
|
(varint->int [2r10000001 2r10101001 2r00000001])
|
||||||
|
(varint->int [2r00000111])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defn bytes->len-type
|
||||||
|
"Prepend byte array with length in varint format"
|
||||||
|
[bytes]
|
||||||
|
(concat (int->varint (count bytes))
|
||||||
|
bytes))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(to-fancy-hex (bytes->len-type (.getBytes "testing")))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defn pack-bytes-as-tlv
|
||||||
|
"Pack the value as TLV.
|
||||||
|
|
||||||
|
Type can one of those 6 IDs:
|
||||||
|
ID Name Used For
|
||||||
|
0 VARINT int32, int64, uint32, uint64, sint32, sint64, bool, enum
|
||||||
|
1 I64 fixed64, sfixed64, double
|
||||||
|
2 LEN string, bytes, embedded messages, packed repeated fields
|
||||||
|
3 SGROUP group start (deprecated)
|
||||||
|
4 EGROUP group end (deprecated)
|
||||||
|
5 I32 fixed32, sfixed32, float
|
||||||
|
"
|
||||||
|
([{:keys [field type value]}]
|
||||||
|
(pack-bytes-as-tlv field type value))
|
||||||
|
([field-number type value]
|
||||||
|
(let [field-displaced (bit-shift-left field-number 3)
|
||||||
|
tag (int->varint (bit-or field-displaced type))]
|
||||||
|
(concat tag value))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn unpack-tlv-bytes
|
||||||
|
"Returns a map with 3 pairs:
|
||||||
|
- :field is the field number
|
||||||
|
- :type is one of the values of WIRE_TYPES
|
||||||
|
- :value is the byte array with the payload"
|
||||||
|
[packed]
|
||||||
|
(let [first-byte (bit-and 2r01111111 (first packed)) ;; bye bye, MSB
|
||||||
|
type (bit-and 2r00000111 first-byte)
|
||||||
|
field-number (bit-shift-right first-byte 3)]
|
||||||
|
{:field field-number :type type :value (rest packed)}))
|
||||||
|
|
||||||
|
|
||||||
45
projects/core/src/totp/otp_proto.clj
Normal file
45
projects/core/src/totp/otp_proto.clj
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
(ns totp.otp-proto
|
||||||
|
(:require [clojobuf.core :refer [protoc decode]]
|
||||||
|
[alphabase.base64 :as b64]
|
||||||
|
[alphabase.base32 :as b32]))
|
||||||
|
|
||||||
|
|
||||||
|
;; Where lookup for proto files
|
||||||
|
(def registry (protoc ["resourcse/proto/"
|
||||||
|
"projects/core/resources/proto/"]
|
||||||
|
["otpauth-migration.proto"]))
|
||||||
|
|
||||||
|
|
||||||
|
(defn parse-data
|
||||||
|
[binary-data]
|
||||||
|
(let [decoded (decode registry :MigrationPayload binary-data)
|
||||||
|
msg (get-in decoded [:otp_parameters 0])]
|
||||||
|
;(println "Decoded:" msg)
|
||||||
|
(when msg
|
||||||
|
{:secret (b32/encode (:secret msg))
|
||||||
|
:name (:name msg)
|
||||||
|
:issuer (:issuer msg)
|
||||||
|
:algorithm (case (:algorithm msg)
|
||||||
|
:ALGORITHM_SHA256 "sha256"
|
||||||
|
:ALGORITHM_SHA512 "sha512"
|
||||||
|
:ALGORITHM_MD5 "md5"
|
||||||
|
"sha1") ;; sha1 by default
|
||||||
|
:digits (case (:digits msg)
|
||||||
|
:DIGIT_COUNT_EIGHT 8
|
||||||
|
6) ;; 6 digits by default
|
||||||
|
:type (case (:type msg)
|
||||||
|
:OTP_TYPE_UNSPECIFIED 0
|
||||||
|
:OTP_TYPE_HOTP 1
|
||||||
|
:OTP_TYPE_TOTP 2) ;; Only TOTP is supported
|
||||||
|
})))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(let [b64-data "CkkKEJ0M4MyHfITKCwCfqPIttjESFHJ1YmVuY2pAMThCMTY5RDVGRjAwGgRTTldMIAEoATACQhMzYjkxMDQxNzI3NzgzNDIzNDYyEAIYASAA"
|
||||||
|
bin-data (b64/decode b64-data)
|
||||||
|
decoded (decode registry :MigrationPayload bin-data)]
|
||||||
|
;(get-in decoded [:otp_parameters 0])
|
||||||
|
(parse-data bin-data)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
(ns totp.core-test
|
(ns totp.core-test
|
||||||
|
#_{:clj-kondo/ignore [:refer-all]}
|
||||||
(:require [clojure.test :refer :all]
|
(:require [clojure.test :refer :all]
|
||||||
[totp.core :refer :all]
|
[totp.core :refer :all]
|
||||||
[alphabase.base64 :as b64])
|
[alphabase.base64 :as b64])
|
||||||
@@ -25,21 +26,21 @@
|
|||||||
(is (= true (bytes-array? (.getBytes ""))))
|
(is (= true (bytes-array? (.getBytes ""))))
|
||||||
(is (= true (bytes-array? (bytes (byte-array [0 0 0 0 0 0 0 0])))))))
|
(is (= true (bytes-array? (bytes (byte-array [0 0 0 0 0 0 0 0])))))))
|
||||||
|
|
||||||
(deftest hmac-sha1-test
|
(deftest hmac-test
|
||||||
(testing "border cases"
|
(testing "border cases"
|
||||||
(is (= nil (hmac-sha1 nil nil)))
|
(is (= nil (hmac nil nil nil)))
|
||||||
(is (= nil (hmac-sha1 "" nil)))
|
(is (= nil (hmac nil "" nil)))
|
||||||
(is (= nil (hmac-sha1 nil "")))
|
(is (= nil (hmac nil nil "")))
|
||||||
(is (= nil (hmac-sha1 (.getBytes "") nil)))
|
(is (= nil (hmac nil (.getBytes "") nil)))
|
||||||
(is (= nil (hmac-sha1 nil (.getBytes ""))))
|
(is (= nil (hmac nil nil (.getBytes ""))))
|
||||||
(is (= "" (hmac-sha1 "" ""))))
|
(is (= "" (hmac "" "" ""))))
|
||||||
(testing "String params"
|
(testing "String params"
|
||||||
(is (= "63h3K4sN+c3NDEl3EGeA23jq/EY=" (hmac-sha1 "12345" "this is a message")))
|
(is (= "63h3K4sN+c3NDEl3EGeA23jq/EY=" (hmac "sha1" "12345" "this is a message")))
|
||||||
(is (= "MA+ieo7t7MeQfyZR/X52dB1aXDI=" (hmac-sha1 "12345" "this is a longer message
|
(is (= "MA+ieo7t7MeQfyZR/X52dB1aXDI=" (hmac "sha1" "12345" "this is a longer message
|
||||||
with some lines"))))
|
with some lines"))))
|
||||||
(testing "byte[] params"
|
(testing "byte[] params"
|
||||||
(is (Arrays/equals (b64/decode "63h3K4sN+c3NDEl3EGeA23jq/EY=") (hmac-sha1 (.getBytes "12345") (.getBytes "this is a message"))))
|
(is (Arrays/equals (b64/decode "63h3K4sN+c3NDEl3EGeA23jq/EY=") (hmac "sha1" (.getBytes "12345") (.getBytes "this is a message"))))
|
||||||
(is (Arrays/equals (b64/decode "MA+ieo7t7MeQfyZR/X52dB1aXDI=") (hmac-sha1 (.getBytes "12345") (.getBytes "this is a longer message
|
(is (Arrays/equals (b64/decode "MA+ieo7t7MeQfyZR/X52dB1aXDI=") (hmac "sha1" (.getBytes "12345") (.getBytes "this is a longer message
|
||||||
with some lines"))))))
|
with some lines"))))))
|
||||||
|
|
||||||
|
|
||||||
@@ -62,10 +63,10 @@
|
|||||||
|
|
||||||
(deftest get-otp-test
|
(deftest get-otp-test
|
||||||
(testing "Border cases"
|
(testing "Border cases"
|
||||||
(is (nil? (get-otp nil nil)))
|
(is (nil? (get-otp nil nil nil nil)))
|
||||||
(is (nil? (get-otp "" nil)))
|
(is (nil? (get-otp "" nil nil nil)))
|
||||||
(is (nil? (get-otp nil "")))
|
(is (nil? (get-otp nil "" nil nil)))
|
||||||
(is (nil? (get-otp nil 1000))))
|
(is (nil? (get-otp nil 1000 nil nil))))
|
||||||
(testing "Common usage"
|
(testing "Common usage"
|
||||||
(is (= "837552" (get-otp "MJXW42LBORXQ====" 10000)))
|
(is (= 6 (count (get-otp "MJXW42LBORXQ====" "sha1" 6 10000))))
|
||||||
(is (= 6 (count (get-otp "MJXW42LBORXQ===="))))))
|
(is (= 6 (count (get-otp "MJXW42LBORXQ===="))))))
|
||||||
149
projects/core/test/totp/otp_import_test.clj
Normal file
149
projects/core/test/totp/otp_import_test.clj
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
(ns totp.otp-import-test
|
||||||
|
#_{:clj-kondo/ignore [:refer-all]}
|
||||||
|
(:require [clojure.test :refer :all]
|
||||||
|
[totp.otp-import :refer :all]
|
||||||
|
[alphabase.bytes :as b]
|
||||||
|
[alphabase.base16 :as hex]
|
||||||
|
[alphabase.base64 :as b64]
|
||||||
|
[alphabase.base32 :as b32])
|
||||||
|
(:import (java.util Arrays)))
|
||||||
|
|
||||||
|
|
||||||
|
(deftest len-bits-test
|
||||||
|
(testing "Check required number of blocks to encode a number in n bits"
|
||||||
|
(is (nil? (len-bits nil 10)))
|
||||||
|
(is (= 0 (len-bits 10 nil)))
|
||||||
|
(is (= 1 (len-bits 2 1)))
|
||||||
|
(is (= 2 (len-bits 2 10)))
|
||||||
|
(is (= 2 (len-bits 2 15)))
|
||||||
|
(is (= 3 (len-bits 2 16)))
|
||||||
|
(is (= 1 (len-bits 8 255)))
|
||||||
|
(is (= 2 (len-bits 7 255)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(deftest len-bytes-test
|
||||||
|
(testing "Check required number of bytes to encode a number"
|
||||||
|
(is (= 0 (len-bytes nil)))
|
||||||
|
(is (= 1 (len-bytes 0)))
|
||||||
|
(is (= 1 (len-bytes 1)))
|
||||||
|
(is (= 1 (len-bytes 255)))
|
||||||
|
(is (= 2 (len-bytes 256)))
|
||||||
|
(is (= 2 (len-bytes 65535)))
|
||||||
|
(is (= 3 (len-bytes 65536)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(deftest integer>bytes-test
|
||||||
|
(testing "Convert several ints to a byte array"
|
||||||
|
(is (nil? (integer>bytes nil)))
|
||||||
|
(is (b/bytes= (b/init-bytes [0]) (integer>bytes 0)))
|
||||||
|
(is (b/bytes= (b/init-bytes [1]) (integer>bytes 1)))
|
||||||
|
(is (b/bytes= (b/init-bytes [-1]) (integer>bytes 255)))
|
||||||
|
(is (b/bytes= (b/init-bytes [1 0]) (integer>bytes 256)))
|
||||||
|
(is (b/bytes= (b/init-bytes [-1 -1]) (integer>bytes 65535)))
|
||||||
|
(is (b/bytes= (b/init-bytes [127 -1 -1 -1 -1 -1 -1 -1]) (integer>bytes Long/MAX_VALUE)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(deftest decimal->base-test
|
||||||
|
(testing "Convert from decimal base to an arbitrary base"
|
||||||
|
(is (= [0] (decimal->base nil 10)))
|
||||||
|
(is (= [0] (decimal->base 10 nil)))
|
||||||
|
(is (= [0] (decimal->base 2 0)))
|
||||||
|
(is (= [0] (decimal->base 0 2)))
|
||||||
|
(is (= [1 0 0 0] (decimal->base 2 8)))
|
||||||
|
(is (= [2 2] (decimal->base 3 8)))
|
||||||
|
(is (= [2 0] (decimal->base 4 8)))
|
||||||
|
(is (= [1 3] (decimal->base 5 8)))
|
||||||
|
(is (= [3 1] (decimal->base 5 8 false)))
|
||||||
|
(is (= [0] (decimal->base 5 0)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(deftest base->decimal-test
|
||||||
|
(testing "Convert from arbitrary base to decimal"
|
||||||
|
(is (zero? (base->decimal 2 nil)))
|
||||||
|
(is (zero? (base->decimal 2 [])))
|
||||||
|
(is (zero? (base->decimal nil [1])))
|
||||||
|
(is (= 8 (base->decimal 2 [1 0 0 0])))
|
||||||
|
(is (= 8 (base->decimal 3 [2 2])))
|
||||||
|
(is (= 8 (base->decimal 4 [2 0])))
|
||||||
|
(is (= 8 (base->decimal 5 [1 3])))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(deftest decimal->base-decimal-test
|
||||||
|
(testing "Check if convert from decimal to a base and back preserves the original number"
|
||||||
|
(is (= 8 (base->decimal 2 (decimal->base 2 8))))
|
||||||
|
(is (= 127 (base->decimal 2 (decimal->base 2 127))))
|
||||||
|
(is (= 417 (base->decimal 13 (decimal->base 13 417))))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(deftest int->varint-test
|
||||||
|
(testing "Convert from integer number (int, long, byte, etc) to varint"
|
||||||
|
(is (Arrays/equals (byte-array [0]) (int->varint nil)))
|
||||||
|
(is (Arrays/equals (byte-array [0]) (int->varint 0)))
|
||||||
|
(is (Arrays/equals (byte-array [2r10010110 2r00000001]) (int->varint 150)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(deftest varint->int-test
|
||||||
|
(testing "Convert from barint to long"
|
||||||
|
(is (= 0 (varint->int nil)))
|
||||||
|
(is (= 0 (varint->int [0])))
|
||||||
|
(is (= 150 (varint->int [-106, 1])))
|
||||||
|
(is (= 150 (varint->int [22 1])))
|
||||||
|
(is (= 151 (varint->int [-105, 1])))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(deftest int->varint->int-test
|
||||||
|
(testing "Convert from int to varint and back to int"
|
||||||
|
(is (= 150 (varint->int (int->varint 150))))
|
||||||
|
(is (= 151 (varint->int (int->varint 151))))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(deftest more-blocks?-test
|
||||||
|
(testing "Check if there are more blocks in a varint type"
|
||||||
|
(is (nil? (more-blocks? nil)))
|
||||||
|
(is (true? (more-blocks? 2r10000001)))
|
||||||
|
(is (true? (more-blocks? 2r11111111)))
|
||||||
|
(is (false? (more-blocks? 2r00000001)))
|
||||||
|
(is (false? (more-blocks? 2r01111111)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(deftest extract-varint-blocks-test
|
||||||
|
(testing "Group varints"
|
||||||
|
(is (empty? (extract-varint-blocks nil)))
|
||||||
|
(is (empty? (extract-varint-blocks [])))
|
||||||
|
(is (= [[2r00000111]] (extract-varint-blocks [2r00000111])))
|
||||||
|
(is (= [[2r00000111] [2r00000111]] (extract-varint-blocks [2r00000111 2r00000111])))
|
||||||
|
(is (= [[2r11111111 2r01111111] [2r10000001 2r10101001 2r00000001] [2r00000111]] (extract-varint-blocks [2r11111111 2r01111111, 2r10000001 2r10101001 2r00000001, 2r00000111])))
|
||||||
|
(is (= [[2r11111111 2r01111111] [2r10000001 2r10101001 2r00000001] [2r10000111]] (extract-varint-blocks [2r11111111 2r01111111, 2r10000001 2r10101001 2r00000001, 2r10000111])))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(deftest extract-varints-test
|
||||||
|
(testing "bytes with varints to decimal"
|
||||||
|
(is (empty? (extract-varints nil)))
|
||||||
|
(is (empty? (extract-varints [])))
|
||||||
|
(is (= [7] (extract-varints [2r00000111])))
|
||||||
|
(is (= [7 7] (extract-varints [2r00000111 2r00000111])))
|
||||||
|
(is (= [16383 21633 7] (extract-varints [2r11111111 2r01111111, 2r10000001 2r10101001 2r00000001, 2r00000111])))
|
||||||
|
(is (= [16383 21633 7] (extract-varints [2r11111111 2r01111111, 2r10000001 2r10101001 2r00000001, 2r10000111])))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
1
projects/core/tests.edn
Normal file
1
projects/core/tests.edn
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#kaocha/v1 {}
|
||||||
8
projects/gui/deps.edn
Executable file
8
projects/gui/deps.edn
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
{:paths ["src" "target/classes"]
|
||||||
|
:deps {;;org.clojure/clojure {:mvn/version "1.12.1"}
|
||||||
|
cli-matic/cli-matic {:mvn/version "0.5.4"} ;; https://github.com/l3nz/cli-matic
|
||||||
|
;; GUI
|
||||||
|
seesaw/seesaw {:mvn/version "1.5.0"}
|
||||||
|
com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}}
|
||||||
|
}
|
||||||
|
|
||||||
172
projects/gui/src/totp/gui.clj
Normal file
172
projects/gui/src/totp/gui.clj
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
(ns totp.gui
|
||||||
|
#_{:clj-kondo/ignore [:refer-all]}
|
||||||
|
(:require [totp.core :refer :all]
|
||||||
|
[totp.data :refer :all]
|
||||||
|
[clojure.pprint :as pp]
|
||||||
|
[seesaw.core :refer :all]
|
||||||
|
[seesaw.mig :refer :all]
|
||||||
|
[seesaw.clipboard :as cp]
|
||||||
|
[seesaw.dev :refer :all])
|
||||||
|
(:import [java.util Date TimerTask Timer])
|
||||||
|
(:gen-class))
|
||||||
|
|
||||||
|
|
||||||
|
(defn copy-handler
|
||||||
|
"Copies TOTP from text with id field-id, to system clipboard"
|
||||||
|
[field-id e]
|
||||||
|
(let [b-name (str "#" field-id)
|
||||||
|
b-obj (select (to-root e) [(keyword b-name)])
|
||||||
|
b-text (config b-obj :text)]
|
||||||
|
(println "Copying text value: " b-text)
|
||||||
|
(cp/contents! b-text)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn make-otp-list
|
||||||
|
"Make panel with OTPs"
|
||||||
|
[]
|
||||||
|
(scrollable
|
||||||
|
(mig-panel
|
||||||
|
:constraints ["wrap 3"
|
||||||
|
"[shrink 0]20px[200, grow, fill]10px[shrink 0]"]
|
||||||
|
:items (let [apps (with-config (filter some? #_{:clj-kondo/ignore [:unresolved-symbol]} cfg))]
|
||||||
|
(reduce (fn [acc a]
|
||||||
|
(let [{:keys [name secret algorithm digits period]} a
|
||||||
|
field-id (str "field-totp-" name)]
|
||||||
|
(-> acc
|
||||||
|
(conj [name])
|
||||||
|
(conj [(text :text (get-otp secret algorithm digits period)
|
||||||
|
:editable? false
|
||||||
|
:id field-id)])
|
||||||
|
(conj [(action :name "copy"
|
||||||
|
:handler (partial copy-handler field-id)
|
||||||
|
:command (str "cmd-" name))]))))
|
||||||
|
[] apps)))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(make-otp-list))
|
||||||
|
|
||||||
|
|
||||||
|
(defn make-time-bar
|
||||||
|
"Make the progress bar with the remaining time"
|
||||||
|
[init-val]
|
||||||
|
(border-panel
|
||||||
|
:hgap 5
|
||||||
|
:center (progress-bar :id "timer-bar"
|
||||||
|
:value init-val
|
||||||
|
:max 30
|
||||||
|
:paint-string? true)
|
||||||
|
:east (text :id "timer-text"
|
||||||
|
:text init-val
|
||||||
|
:editable? false
|
||||||
|
:columns 2
|
||||||
|
:halign :right)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn make-add-frame
|
||||||
|
[parent]
|
||||||
|
(frame :title "Add new TOTP"
|
||||||
|
:minimum-size [320 :by 200]
|
||||||
|
:size [320 :by 220]
|
||||||
|
:on-close :dispose
|
||||||
|
:content (border-panel
|
||||||
|
:center (mig-panel
|
||||||
|
:constraints ["wrap 2"
|
||||||
|
"[shrink 0]20px[200, grow, fill]"]
|
||||||
|
:items [["Name"] [(text :id "add-name" :columns 32)]
|
||||||
|
["Secret (B32)"] [(text :id "add-secret" :columns 32)]
|
||||||
|
["User (optional)"] [(text :id "add-user" :columns 32)]
|
||||||
|
["Issuer (optional)"] [(text :id "add-issuer" :columns 32)]
|
||||||
|
["Algorithm"] [(combobox :model ["sha1" "sha256" "sha512"])]
|
||||||
|
["Digits"] [(combobox :model ["6" "8"])]])
|
||||||
|
:south (flow-panel :align :right :items [(action :name "Cancel")
|
||||||
|
(action :name "Add")]))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn make-main-buttons
|
||||||
|
[]
|
||||||
|
(flow-panel :align :right
|
||||||
|
:items ["button a" "button b"]))
|
||||||
|
|
||||||
|
|
||||||
|
(defn make-frame-content
|
||||||
|
[]
|
||||||
|
(border-panel :hgap 10 :vgap 10
|
||||||
|
:center (make-otp-list)
|
||||||
|
:north (make-time-bar (int (/ (calculate-offset-millis 30) 1000)))
|
||||||
|
:south (make-main-buttons)
|
||||||
|
;:east "EAST"
|
||||||
|
;:west "WEST"
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
(defn make-frame
|
||||||
|
"Make main frame's content"
|
||||||
|
[]
|
||||||
|
(frame :title "TOTP",
|
||||||
|
:content (make-frame-content)
|
||||||
|
;:minimum-size [320 :by 240]
|
||||||
|
;;:on-close :exit
|
||||||
|
:on-close :dispose))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn update-totps
|
||||||
|
"Update all totps"
|
||||||
|
[root]
|
||||||
|
(let [apps (with-config (filter some? #_{:clj-kondo/ignore [:unresolved-symbol]} cfg))]
|
||||||
|
(doseq [app apps]
|
||||||
|
(let [{:keys [name secret algorithm digits period]} app
|
||||||
|
field-id (str "field-totp-" name)
|
||||||
|
field (select root [(keyword (str "#" field-id))])
|
||||||
|
current-otp (get-otp secret algorithm digits period)]
|
||||||
|
;;(println "Updating" field-id "with otp" current-otp)
|
||||||
|
(config! field :text current-otp)))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn update-progress
|
||||||
|
[root]
|
||||||
|
(let [time-bar (select root [:#timer-bar])
|
||||||
|
time-text (select root [:#timer-text])
|
||||||
|
offset (inc (int (/ (calculate-offset-millis 30) 1000)))]
|
||||||
|
;;(println "Updating at at" (System/currentTimeMillis))
|
||||||
|
(config! time-bar :value offset)
|
||||||
|
(config! time-text :text offset)
|
||||||
|
(when (= 1 offset)
|
||||||
|
;;(println "update TOTP")
|
||||||
|
(update-totps root))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn start-updater
|
||||||
|
[root]
|
||||||
|
(let [now (System/currentTimeMillis)
|
||||||
|
now-seconds (int (/ now 1000))
|
||||||
|
delay (- 1000 (- now (* 1000 now-seconds)))]
|
||||||
|
(. (new Timer) (scheduleAtFixedRate
|
||||||
|
(proxy [TimerTask] []
|
||||||
|
(run [] (update-progress root)))
|
||||||
|
delay 1000))
|
||||||
|
(println "Now" now "Delay" delay)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn -main [& args]
|
||||||
|
(native!)
|
||||||
|
(invoke-later
|
||||||
|
(-> (make-frame)
|
||||||
|
pack!
|
||||||
|
show!
|
||||||
|
(make-add-frame)
|
||||||
|
start-updater))
|
||||||
|
(println "Gui started"))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; This kills your REPL connection
|
||||||
|
(-main)
|
||||||
|
|
||||||
|
(show-options (frame))
|
||||||
|
(show-options (text))
|
||||||
|
|
||||||
|
(-> (make-add-frame nil)
|
||||||
|
(pack!)
|
||||||
|
(show!)))
|
||||||
18
projects/web/deps.edn
Executable file
18
projects/web/deps.edn
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
{:paths ["src" "resources" "target/classes"]
|
||||||
|
:deps {;clj-totp/core {:local/root "../core"}
|
||||||
|
org.clojure/clojure {:mvn/version "1.12.1"}
|
||||||
|
;; For SQLite
|
||||||
|
com.github.seancorfield/next.jdbc {:mvn/version "1.3.1048"}
|
||||||
|
org.xerial/sqlite-jdbc {:mvn/version "3.50.3.0"}
|
||||||
|
;; For Datomic local
|
||||||
|
com.datomic/local {:mvn/version "1.0.291"};; https://docs.datomic.com/datomic-local.html
|
||||||
|
}
|
||||||
|
|
||||||
|
:aliases {;; Execute the app
|
||||||
|
;:run {:main-opts ["-m" "totp.app"]}
|
||||||
|
|
||||||
|
;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
||||||
|
:test {:extra-paths ["test"]
|
||||||
|
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
|
||||||
|
:main-opts ["-m" "kaocha.runner"]}}}
|
||||||
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
(ns totp.db.datomic
|
(ns totp.db.datomic
|
||||||
(:require [datomic.client.api :as d]))
|
(:require [totp.data :as data]
|
||||||
|
[datomic.client.api :as d]))
|
||||||
|
|
||||||
|
|
||||||
|
(def cfg-path (data/join-path (System/getProperty "user.home") ".config" "totp"))
|
||||||
(def db-path (str cfg-path java.io.File/separator "data"))
|
(def db-path (str cfg-path java.io.File/separator "data"))
|
||||||
|
|
||||||
(def cfg {:server-type :datomic-local
|
(def cfg {:server-type :datomic-local
|
||||||
1
projects/web/tests.edn
Normal file
1
projects/web/tests.edn
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#kaocha/v1 {}
|
||||||
1812
reflect_config.json
1812
reflect_config.json
File diff suppressed because it is too large
Load Diff
270
reflect_config.json.bak
Normal file
270
reflect_config.json.bak
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "com.sun.crypto.provider.HmacSHA1",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "java.lang.reflect.Method",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "canAccess",
|
||||||
|
"parameterTypes": [
|
||||||
|
"java.lang.Object"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "java.util.Arrays",
|
||||||
|
"allDeclaredClasses": true,
|
||||||
|
"allPublicClasses": true,
|
||||||
|
"queryAllPublicMethods": true,
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "copyOfRange",
|
||||||
|
"parameterTypes": [
|
||||||
|
"byte[]",
|
||||||
|
"int",
|
||||||
|
"int"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "java.util.Timer",
|
||||||
|
"queryAllPublicMethods": true,
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "scheduleAtFixedRate",
|
||||||
|
"parameterTypes": [
|
||||||
|
"java.util.TimerTask",
|
||||||
|
"long",
|
||||||
|
"long"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "java.util.concurrent.locks.Lock"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "java.util.concurrent.locks.ReentrantLock"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_postprocess$eval194",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_postprocess$eval209",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_postprocess$eval224",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_postprocess$eval242",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_postprocess$eval331",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_postprocess__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_preprocess$eval148",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_preprocess$eval15",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_preprocess$eval179",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_preprocess$eval254",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_preprocess$eval265",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_preprocess$eval279",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_preprocess$eval290",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_preprocess$eval301",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_preprocess$eval314",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_preprocess$eval321",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_preprocess__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ast_util__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.core__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.ebnf__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.parse__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.parse_textformat__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rubberbuf.util__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf.constant__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf.core__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf.decode__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf.encode__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf.schema$eval367",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf.schema$eval396",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf.schema__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf.util__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf_codec.decode__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf_codec.deserialize__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf_codec.encode__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf_codec.io.reader.ByteReader"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf_codec.io.reader__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf_codec.io.writer.ByteWriter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf_codec.io.writer__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf_codec.serialize__init"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"clojobuf_codec.util__init"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
(ns totp.app
|
|
||||||
(:require [totp.core :refer :all]
|
|
||||||
[totp.data :refer :all]
|
|
||||||
[cli-matic.core :refer [run-cmd]]
|
|
||||||
[cli-matic.utils :as U]
|
|
||||||
[clojure.pprint :as pp])
|
|
||||||
(:import [java.util TimerTask Timer])
|
|
||||||
(:gen-class))
|
|
||||||
|
|
||||||
|
|
||||||
(defn- print-confinuous
|
|
||||||
([secret] (print-confinuous secret 30))
|
|
||||||
([secret step]
|
|
||||||
(let [step-millis (* 1000 step)
|
|
||||||
now (System/currentTimeMillis)
|
|
||||||
delay (int (- step-millis (rem now step-millis)))
|
|
||||||
fn-show (fn [s] (println (System/currentTimeMillis) "-> "(get-otp s)))
|
|
||||||
task (proxy [TimerTask] []
|
|
||||||
(run [] (fn-show secret)))]
|
|
||||||
(println "\n <Generating continuosly, press enter to stop>\n")
|
|
||||||
;; (println "Now:" now ", Delay:" delay ", Next execution: " (+ now delay))
|
|
||||||
(fn-show secret)
|
|
||||||
(. (new Timer) (scheduleAtFixedRate task delay step-millis)))
|
|
||||||
(read-line))) ;; Waits for a key press
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(print get-otp "MJXW42LBORXQ====")
|
|
||||||
(print-confinuous "MJXW42LBORXQ====")
|
|
||||||
)
|
|
||||||
|
|
||||||
(defn cmd-generate
|
|
||||||
[& {:keys [secret continuous] :as otps}]
|
|
||||||
;(pp/pprint otps)
|
|
||||||
(if continuous
|
|
||||||
(print-confinuous secret)
|
|
||||||
(println (get-otp secret))
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
(def cli-options
|
|
||||||
{:app {:command "totp"
|
|
||||||
:version "1.0.0"
|
|
||||||
:description ["Generate a TOTP"]}
|
|
||||||
|
|
||||||
:commands [{:command "generate" :short "g"
|
|
||||||
:description "Generate one TOTP with a given secret in BASE32"
|
|
||||||
:examples ["totp generate \"MJXW42LBORXQ====\""
|
|
||||||
"totp g \"MJXW42LBORXQ====\""]
|
|
||||||
:opts [{:option "secret"
|
|
||||||
:short 0
|
|
||||||
:as "Secret codified in BASE32"
|
|
||||||
:type :string
|
|
||||||
:default :present}
|
|
||||||
{:option "continuous"
|
|
||||||
:short "c"
|
|
||||||
:type :with-flag
|
|
||||||
:as "Contiuous mode"
|
|
||||||
:default false}]
|
|
||||||
:runs cmd-generate}]})
|
|
||||||
|
|
||||||
|
|
||||||
(defn -main [& args]
|
|
||||||
(run-cmd args cli-options))
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
(ns totp.core
|
|
||||||
(:require [alphabase.base32 :as b32])
|
|
||||||
(:import (javax.crypto Mac)
|
|
||||||
(javax.crypto.spec SecretKeySpec)
|
|
||||||
(java.util Base64 Arrays)
|
|
||||||
(java.nio ByteBuffer)))
|
|
||||||
|
|
||||||
(def ^:private byte-array-type (type (.getBytes "")))
|
|
||||||
|
|
||||||
(defn timestamp->steps
|
|
||||||
"Converts from UNIX timestamp in milliseconds to a number os steps of 's' seconds of duration"
|
|
||||||
[time, step-size]
|
|
||||||
(if (or (nil? time) (nil? step-size) (zero? step-size))
|
|
||||||
0
|
|
||||||
(int (quot time (* 1000 step-size)))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn bytes-array?
|
|
||||||
"Return true if x is a byte[]"
|
|
||||||
[x]
|
|
||||||
(= byte-array-type (type x)))
|
|
||||||
|
|
||||||
|
|
||||||
(defmulti hmac-sha1
|
|
||||||
"Generates an HMAC-SHA1. The key and the message can be (both) string or array of bytes, nil otherwise"
|
|
||||||
(fn [key message]
|
|
||||||
(cond
|
|
||||||
(and (string? key) (string? message)) :string
|
|
||||||
(and (bytes-array? key) (bytes-array? message)) :byte
|
|
||||||
:else :nil)))
|
|
||||||
|
|
||||||
;; By default
|
|
||||||
(defmethod hmac-sha1 :nil [_ _]
|
|
||||||
nil)
|
|
||||||
|
|
||||||
;; When key and message are strings
|
|
||||||
(defmethod hmac-sha1 :string [key message]
|
|
||||||
(if (or (empty? key) (empty? message))
|
|
||||||
""
|
|
||||||
(let [mac (doto (Mac/getInstance "HmacSHA1") (.init (SecretKeySpec. (.getBytes key) "HmacSHA1")))
|
|
||||||
hmac-bytes (.doFinal mac (.getBytes message))]
|
|
||||||
;; Return the Base64 encoded HMAC
|
|
||||||
(.encodeToString (Base64/getEncoder) hmac-bytes))))
|
|
||||||
|
|
||||||
;; When key and message are arrays of bytes
|
|
||||||
(defmethod hmac-sha1 :byte [key message]
|
|
||||||
(if (nil? message)
|
|
||||||
(bytes (byte-array 0))
|
|
||||||
(let [mac (doto (Mac/getInstance "HmacSHA1") (.init (SecretKeySpec. key "HmacSHA1")))
|
|
||||||
hmac-bytes (.doFinal mac message)]
|
|
||||||
;; Return the Base64 encoded HMAC
|
|
||||||
(Base64/getEncoder) hmac-bytes)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn long->bytes
|
|
||||||
"Converts a long to an array of 8 bytes"
|
|
||||||
[l]
|
|
||||||
;;Java equivalent: ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(someLong).array();
|
|
||||||
(when (integer? l)
|
|
||||||
(-> (ByteBuffer/allocate (/ Long/SIZE Byte/SIZE))
|
|
||||||
(.putLong l)
|
|
||||||
(.array))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn bytes->int
|
|
||||||
"Converts an array of 4 bytes to an integer"
|
|
||||||
[bytes]
|
|
||||||
;;Java equivalent: ByteBuffer.wrap(data).getInt()
|
|
||||||
(when (some? bytes)
|
|
||||||
(.getInt (ByteBuffer/wrap bytes))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn get-otp
|
|
||||||
"Generate an OTP with the given secret (in base32) for the specified timestep"
|
|
||||||
([secret step]
|
|
||||||
(when (and secret step)
|
|
||||||
(let [k (b32/decode secret)
|
|
||||||
c (long->bytes step)
|
|
||||||
hs (hmac-sha1 k c)
|
|
||||||
offset (bit-and (get hs (dec (count hs))) 0x0f) ;; int offset = hs[hs.length-1] & 0xf;
|
|
||||||
chunk (Arrays/copyOfRange hs offset (+ offset 4)) ;(take 4 (drop offset hs)) ;; byte[] chunk = Arrays.copyOfRange(hs, offset, offset+4)
|
|
||||||
]
|
|
||||||
(format "%06d" (-> chunk
|
|
||||||
(bytes->int)
|
|
||||||
(bit-and 0x7fffffff)
|
|
||||||
(rem 1000000))))))
|
|
||||||
([secret]
|
|
||||||
(get-otp secret (timestamp->steps (System/currentTimeMillis) 30))))
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
(ns totp.data
|
|
||||||
(:require [clojure.edn :as e]
|
|
||||||
[clojure.string :as str]
|
|
||||||
[clojure.java.io :as io]
|
|
||||||
[clojure.pprint :as pp]))
|
|
||||||
|
|
||||||
(defn join-path
|
|
||||||
"Joins several subpaths using system's path separator (/ un *NIX and \\ in windows)"
|
|
||||||
[& col]
|
|
||||||
(str/join java.io.File/separator col))
|
|
||||||
|
|
||||||
|
|
||||||
(def cfg-path (join-path (System/getProperty "user.home") ".config" "totp"))
|
|
||||||
|
|
||||||
(def cfg-file (join-path cfg-path "apps.edn"))
|
|
||||||
|
|
||||||
(def cfg-header ";; clj-totp configuration file
|
|
||||||
;; This file contents a list of maps with :name and :secret entries
|
|
||||||
;; Secrets must be encoded in BASE32
|
|
||||||
|
|
||||||
")
|
|
||||||
|
|
||||||
|
|
||||||
(defn exists-config
|
|
||||||
"Checks if the config file exists"
|
|
||||||
[]
|
|
||||||
(.exists (io/file cfg-file)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn create-cfg-file
|
|
||||||
"Creates the config file"
|
|
||||||
[]
|
|
||||||
(println "Creating " cfg-file)
|
|
||||||
(io/delete-file cfg-file true)
|
|
||||||
(io/make-parents cfg-file)
|
|
||||||
(spit cfg-file cfg-header)
|
|
||||||
true)
|
|
||||||
|
|
||||||
|
|
||||||
(defn create-cfg?
|
|
||||||
"Create configuration file if not exists. Overridable with allways = true"
|
|
||||||
([] (create-cfg? false))
|
|
||||||
([allways]
|
|
||||||
(if (or allways (not (exists-config)))
|
|
||||||
(create-cfg-file)
|
|
||||||
false)))
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(exists-config)
|
|
||||||
(create-cfg?)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
(defn load-config
|
|
||||||
"Loads configuration from file"
|
|
||||||
[]
|
|
||||||
(e/read-string (slurp cfg-file)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn store-config
|
|
||||||
"Store configuration to file"
|
|
||||||
[cfg]
|
|
||||||
(when cfg
|
|
||||||
(spit cfg-file (str cfg-header (with-out-str
|
|
||||||
(binding [pp/*print-right-margin* 50]
|
|
||||||
(pp/pprint cfg)))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn delete-app
|
|
||||||
[cfg name]
|
|
||||||
(filter #(not= name (:name %)) cfg))
|
|
||||||
|
|
||||||
|
|
||||||
(defn add-app
|
|
||||||
[cfg name secret]
|
|
||||||
(conj (delete-app cfg name) {:name name :secret secret}))
|
|
||||||
|
|
||||||
|
|
||||||
(defn list-apps
|
|
||||||
[cfg]
|
|
||||||
(map :name cfg))
|
|
||||||
|
|
||||||
|
|
||||||
(defn get-app
|
|
||||||
[cfg name]
|
|
||||||
(first (filter #(= name (:name %)) cfg)))
|
|
||||||
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(exists-config)
|
|
||||||
(create-cfg?)
|
|
||||||
(load-config)
|
|
||||||
|
|
||||||
(with-out-str
|
|
||||||
(binding [pp/*print-right-margin* 50]
|
|
||||||
(pp/pprint [{:name "abc" :secret "def"} {:name "my-app" :secret "abc123"}])))
|
|
||||||
|
|
||||||
(store-config [{:name "abc" :secret "def"} {:name "my-app" :secret "abc123"}])
|
|
||||||
|
|
||||||
(-> nil
|
|
||||||
(add-app "app1" "abc123abc123")
|
|
||||||
(add-app "app2" "abc123abc123")
|
|
||||||
(add-app "app1" "123456789012")
|
|
||||||
(store-config))
|
|
||||||
|
|
||||||
)
|
|
||||||
@@ -1 +1,6 @@
|
|||||||
#kaocha/v1 {}
|
#kaocha/v1
|
||||||
|
{:tests [{:test-paths ["projects/core/src" "projects/core/test"]}]
|
||||||
|
:plugins [:kaocha.plugin/cloverage]
|
||||||
|
:cloverage/opts {:src-ns-path ["projects/core/src" "projects/core/test"]
|
||||||
|
:ns-regex ["totp\\..*(?<!test)$"] ;; All starting with "totp" but not ending by "test"
|
||||||
|
}}
|
||||||
812
use_example.cast
Normal file
812
use_example.cast
Normal file
@@ -0,0 +1,812 @@
|
|||||||
|
{"version": 2, "width": 130, "height": 40, "timestamp": 1759268913, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}}
|
||||||
|
[0.176989, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:48 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[1.762001, "o", "#"]
|
||||||
|
[3.322002, "o", " "]
|
||||||
|
[4.343358, "o", "L"]
|
||||||
|
[5.091238, "o", "\b\u001b[K"]
|
||||||
|
[5.587452, "o", "S"]
|
||||||
|
[5.746296, "o", "h"]
|
||||||
|
[5.797527, "o", "o"]
|
||||||
|
[5.877536, "o", "w"]
|
||||||
|
[6.021713, "o", " "]
|
||||||
|
[6.138177, "o", "a"]
|
||||||
|
[6.262353, "o", "l"]
|
||||||
|
[6.439976, "o", "l"]
|
||||||
|
[6.559555, "o", " "]
|
||||||
|
[6.698153, "o", "o"]
|
||||||
|
[6.854063, "o", "p"]
|
||||||
|
[6.949519, "o", "t"]
|
||||||
|
[7.743221, "o", "i"]
|
||||||
|
[7.810171, "o", "o"]
|
||||||
|
[8.002195, "o", "n"]
|
||||||
|
[8.102527, "o", "s"]
|
||||||
|
[9.149834, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[9.164586, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:48 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[13.250329, "o", "\u001b[7mtotp\u001b[27m"]
|
||||||
|
[14.043554, "o", "\r\u001b[C\u001b[Ctotp\r\n\u001b[?2004l\r"]
|
||||||
|
[14.056176, "o", "** ERROR: **\r\nNo sub-command specified.\r\n\r\n\r\n"]
|
||||||
|
[14.056426, "o", "NAME:\r\n totp - Generate a TOTP\r\n\r\nUSAGE:\r\n totp [global-options] command [command options] [arguments...]\r\n\r\nVERSION:\r\n 1.2\r\n\r\nCOMMANDS:\r\n generate Generate one TOTP for a BASE32 secret, ignoring configured apps\r\n get, g Generate one TOTP for a configured app\r\n config, c Manage configuration\r\n list, l List existing apps\r\n add, a Add a new application with an unique name\r\n import, i Import a TOTP config from a URL\r\n delete, d Removes an existing application by it's unique name\r\n\r\nGLOBAL OPTIONS:\r\n -?, --help\r\n\r\n"]
|
||||||
|
[14.075216, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:48 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;204;36;29m\u001b[0m "]
|
||||||
|
[17.138979, "o", "#"]
|
||||||
|
[17.583635, "o", " "]
|
||||||
|
[17.929988, "o", "g"]
|
||||||
|
[18.069244, "o", "e"]
|
||||||
|
[18.198196, "o", "n"]
|
||||||
|
[18.270251, "o", "e"]
|
||||||
|
[18.318014, "o", "r"]
|
||||||
|
[18.417589, "o", "a"]
|
||||||
|
[18.574066, "o", "t"]
|
||||||
|
[18.649313, "o", "e"]
|
||||||
|
[18.850539, "o", " "]
|
||||||
|
[19.006152, "o", "a"]
|
||||||
|
[19.126994, "o", " "]
|
||||||
|
[19.34653, "o", "s"]
|
||||||
|
[19.430616, "o", "i"]
|
||||||
|
[19.583429, "o", "m"]
|
||||||
|
[19.675425, "o", "p"]
|
||||||
|
[20.261871, "o", "l"]
|
||||||
|
[20.398368, "o", "e"]
|
||||||
|
[20.567488, "o", " "]
|
||||||
|
[20.886076, "o", "t"]
|
||||||
|
[21.001264, "o", "o"]
|
||||||
|
[21.125369, "o", "t"]
|
||||||
|
[21.241288, "o", "p"]
|
||||||
|
[22.32187, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[22.336755, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:48 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;204;36;29m\u001b[0m "]
|
||||||
|
[34.302699, "o", "\u001b[7mtotp\u001b[27m"]
|
||||||
|
[35.866271, "o", "\r\u001b[C\u001b[C\u001b[1Ptot"]
|
||||||
|
[37.185679, "o", "t"]
|
||||||
|
[37.617081, "o", "\b\u001b[K"]
|
||||||
|
[37.989199, "o", "p"]
|
||||||
|
[38.081063, "o", " "]
|
||||||
|
[43.738456, "o", "\u001b[7mgenerate JBSWY3DPEHPK3PXP\u001b[27m"]
|
||||||
|
[44.621944, "o", "\r\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[Cgenerate JBSWY3DPEHPK3PXP\r\n\u001b[?2004l\r"]
|
||||||
|
[44.635244, "o", "159410\r\n"]
|
||||||
|
[44.654801, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:49 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[47.843229, "o", "#"]
|
||||||
|
[48.209818, "o", " "]
|
||||||
|
[48.413046, "o", "n"]
|
||||||
|
[48.505043, "o", "i"]
|
||||||
|
[48.877725, "o", "c"]
|
||||||
|
[49.098866, "o", "e"]
|
||||||
|
[49.457645, "o", ","]
|
||||||
|
[49.516937, "o", " "]
|
||||||
|
[49.864976, "o", "l"]
|
||||||
|
[50.525075, "o", "\b\u001b[K"]
|
||||||
|
[50.945054, "o", "n"]
|
||||||
|
[51.000961, "o", "o"]
|
||||||
|
[51.133706, "o", "w"]
|
||||||
|
[51.465572, "o", ","]
|
||||||
|
[51.537081, "o", " "]
|
||||||
|
[53.649628, "o", "g"]
|
||||||
|
[53.816856, "o", "e"]
|
||||||
|
[53.921891, "o", "n"]
|
||||||
|
[53.969913, "o", "e"]
|
||||||
|
[54.630876, "o", "r"]
|
||||||
|
[54.725743, "o", "a"]
|
||||||
|
[54.861157, "o", "t"]
|
||||||
|
[54.932831, "o", "e"]
|
||||||
|
[55.074689, "o", " "]
|
||||||
|
[55.6697, "o", "*"]
|
||||||
|
[55.909582, "o", "a"]
|
||||||
|
[56.0267, "o", "n"]
|
||||||
|
[56.065065, "o", "d"]
|
||||||
|
[56.37366, "o", "*"]
|
||||||
|
[56.521083, "o", " "]
|
||||||
|
[56.734656, "o", "u"]
|
||||||
|
[56.921009, "o", "p"]
|
||||||
|
[57.025878, "o", "d"]
|
||||||
|
[57.105928, "o", "a"]
|
||||||
|
[57.221608, "o", "t"]
|
||||||
|
[57.29692, "o", "e"]
|
||||||
|
[58.005352, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[58.020059, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:49 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[64.138603, "o", "\u001b[7mtotp generate -c JBSWY3DPEHPK3PXP\u001b[27m"]
|
||||||
|
[65.09814, "o", "\r\u001b[C\u001b[Ctotp generate -c JBSWY3DPEHPK3PXP\r\n\u001b[?2004l\r"]
|
||||||
|
[65.110699, "o", "\r\n <Generating continuosly, press enter to stop>\r\n\r\n"]
|
||||||
|
[65.111, "o", "Refresing in 21 seconds\r\n"]
|
||||||
|
[65.111575, "o", "\r\n[1759268978747] 908667\r\n\r\n"]
|
||||||
|
[65.112729, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[39m 9/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[66.114963, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[39m 10/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[67.117234, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[39m 11/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[68.12148, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[39m 12/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[69.125246, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[39m 13/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[70.129928, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[39m 14/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[70.141157, "o", "\r\n"]
|
||||||
|
[70.161973, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:49 \u001b[0m\u001b[38;2;60;56;54m \u001b[0mtook \u001b[1;33m5s\u001b[0m \r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[72.341232, "o", "#"]
|
||||||
|
[73.225094, "o", " "]
|
||||||
|
[76.03286, "o", "l"]
|
||||||
|
[76.176549, "o", "e"]
|
||||||
|
[76.540956, "o", "\b\u001b[K"]
|
||||||
|
[76.692529, "o", "\b\u001b[K"]
|
||||||
|
[77.10447, "o", "w"]
|
||||||
|
[77.220492, "o", "i"]
|
||||||
|
[77.384462, "o", " "]
|
||||||
|
[77.744525, "o", "\b\u001b[K"]
|
||||||
|
[77.912457, "o", "\b\u001b[K"]
|
||||||
|
[78.08847, "o", "\b\u001b[K"]
|
||||||
|
[80.10545, "o", "w"]
|
||||||
|
[80.201343, "o", "e"]
|
||||||
|
[80.376923, "o", " "]
|
||||||
|
[80.797294, "o", "w"]
|
||||||
|
[80.86962, "o", "i"]
|
||||||
|
[81.096974, "o", "l"]
|
||||||
|
[81.256371, "o", "l"]
|
||||||
|
[81.352347, "o", " "]
|
||||||
|
[81.740674, "o", "s"]
|
||||||
|
[81.992616, "o", "t"]
|
||||||
|
[82.12436, "o", "o"]
|
||||||
|
[82.240303, "o", "r"]
|
||||||
|
[82.297093, "o", "e"]
|
||||||
|
[82.421136, "o", " "]
|
||||||
|
[84.272464, "o", "t"]
|
||||||
|
[84.372248, "o", "h"]
|
||||||
|
[84.496211, "o", "i"]
|
||||||
|
[84.622221, "o", "s"]
|
||||||
|
[84.746303, "o", " "]
|
||||||
|
[84.921847, "o", "c"]
|
||||||
|
[84.9407, "o", "o"]
|
||||||
|
[85.02135, "o", "n"]
|
||||||
|
[85.105464, "o", "f"]
|
||||||
|
[85.216459, "o", "i"]
|
||||||
|
[85.301386, "o", "g"]
|
||||||
|
[85.432453, "o", "u"]
|
||||||
|
[85.602839, "o", "r"]
|
||||||
|
[85.950362, "o", "\b\u001b[K"]
|
||||||
|
[86.096569, "o", "\b\u001b[K"]
|
||||||
|
[86.252261, "o", "\b\u001b[K"]
|
||||||
|
[86.384954, "o", "\b\u001b[K"]
|
||||||
|
[86.504287, "o", "\b\u001b[K"]
|
||||||
|
[86.636345, "o", "\b\u001b[K"]
|
||||||
|
[86.768411, "o", "\b\u001b[K"]
|
||||||
|
[87.220431, "o", "\b\u001b[K"]
|
||||||
|
[87.448385, "o", "d"]
|
||||||
|
[87.562457, "o", "a"]
|
||||||
|
[87.684574, "o", "t"]
|
||||||
|
[87.776284, "o", "e"]
|
||||||
|
[88.772657, "o", "\b\u001b[K"]
|
||||||
|
[88.896276, "o", "a"]
|
||||||
|
[90.472592, "o", " "]
|
||||||
|
[90.652377, "o", "u"]
|
||||||
|
[90.776294, "o", "s"]
|
||||||
|
[90.920295, "o", "i"]
|
||||||
|
[91.084256, "o", "n"]
|
||||||
|
[91.184256, "o", "g"]
|
||||||
|
[91.292257, "o", " "]
|
||||||
|
[91.380229, "o", "l"]
|
||||||
|
[91.560883, "o", "o"]
|
||||||
|
[91.648839, "o", "c"]
|
||||||
|
[91.765128, "o", "a"]
|
||||||
|
[91.864932, "o", "l"]
|
||||||
|
[91.992656, "o", " "]
|
||||||
|
[92.120836, "o", "c"]
|
||||||
|
[92.160275, "o", "o"]
|
||||||
|
[92.236934, "o", "n"]
|
||||||
|
[92.317385, "o", "f"]
|
||||||
|
[92.405098, "o", "i"]
|
||||||
|
[92.509016, "o", "g"]
|
||||||
|
[92.572204, "o", "u"]
|
||||||
|
[92.689152, "o", "r"]
|
||||||
|
[92.773425, "o", "a"]
|
||||||
|
[92.896795, "o", "t"]
|
||||||
|
[92.960195, "o", "i"]
|
||||||
|
[93.016197, "o", "o"]
|
||||||
|
[93.056162, "o", "n"]
|
||||||
|
[93.684574, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[93.699621, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:50 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[95.700291, "o", "#"]
|
||||||
|
[96.017982, "o", "f"]
|
||||||
|
[96.14066, "o", "i"]
|
||||||
|
[96.260872, "o", "r"]
|
||||||
|
[96.388124, "o", "s"]
|
||||||
|
[96.548141, "o", "t"]
|
||||||
|
[96.748096, "o", " "]
|
||||||
|
[97.684556, "o", "\b\u001b[K"]
|
||||||
|
[97.960221, "o", ","]
|
||||||
|
[98.068189, "o", " "]
|
||||||
|
[98.41609, "o", "w"]
|
||||||
|
[98.877924, "o", "\b\u001b[K"]
|
||||||
|
[100.213156, "o", "w"]
|
||||||
|
[100.341245, "o", "h"]
|
||||||
|
[100.430133, "o", "e"]
|
||||||
|
[100.536017, "o", "r"]
|
||||||
|
[100.635989, "o", "e"]
|
||||||
|
[100.793695, "o", " "]
|
||||||
|
[100.997081, "o", "i"]
|
||||||
|
[101.073151, "o", "s"]
|
||||||
|
[101.236518, "o", " "]
|
||||||
|
[101.372497, "o", "t"]
|
||||||
|
[101.472003, "o", "h"]
|
||||||
|
[101.551994, "o", "e"]
|
||||||
|
[101.684209, "o", " "]
|
||||||
|
[102.634076, "o", "c"]
|
||||||
|
[102.716541, "o", "o"]
|
||||||
|
[102.809801, "o", "n"]
|
||||||
|
[102.897178, "o", "f"]
|
||||||
|
[103.033125, "o", "i"]
|
||||||
|
[103.116694, "o", "g"]
|
||||||
|
[103.231916, "o", "u"]
|
||||||
|
[103.3456, "o", "r"]
|
||||||
|
[103.401105, "o", "a"]
|
||||||
|
[103.552214, "o", "t"]
|
||||||
|
[103.607996, "o", "i"]
|
||||||
|
[103.671962, "o", "o"]
|
||||||
|
[103.731929, "o", "n"]
|
||||||
|
[103.82512, "o", " "]
|
||||||
|
[103.968845, "o", "f"]
|
||||||
|
[104.065441, "o", "i"]
|
||||||
|
[104.245532, "o", "l"]
|
||||||
|
[104.308966, "o", "e"]
|
||||||
|
[104.592765, "o", "?"]
|
||||||
|
[104.949829, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[104.966403, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:50 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[110.735684, "o", "\u001b[7mtotp config info\u001b[27m"]
|
||||||
|
[111.585646, "o", "\r\u001b[C\u001b[Ctotp config info\r\n\u001b[?2004l\r"]
|
||||||
|
[111.599116, "o", "Configuration file: not found. Expected location: /home/ruben/.config/totp/apps.edn\r\n"]
|
||||||
|
[111.61847, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:50 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[117.168242, "o", "n"]
|
||||||
|
[117.215739, "o", "o"]
|
||||||
|
[117.355702, "o", " "]
|
||||||
|
[117.733603, "o", "\b\u001b[K"]
|
||||||
|
[117.908862, "o", "\b\u001b[K"]
|
||||||
|
[118.077682, "o", "\b\u001b[K"]
|
||||||
|
[118.444922, "o", "#"]
|
||||||
|
[118.693748, "o", " "]
|
||||||
|
[118.885488, "o", "n"]
|
||||||
|
[118.944353, "o", "o"]
|
||||||
|
[119.044626, "o", " "]
|
||||||
|
[119.212729, "o", "f"]
|
||||||
|
[119.325414, "o", "o"]
|
||||||
|
[119.384939, "o", "n"]
|
||||||
|
[119.640383, "o", "g"]
|
||||||
|
[119.936985, "o", "\b\u001b[K"]
|
||||||
|
[120.072503, "o", "\b\u001b[K"]
|
||||||
|
[120.216486, "o", "\b\u001b[K"]
|
||||||
|
[120.36447, "o", "\b\u001b[K"]
|
||||||
|
[120.471851, "o", "c"]
|
||||||
|
[120.584431, "o", "o"]
|
||||||
|
[120.65574, "o", "n"]
|
||||||
|
[120.731969, "o", "f"]
|
||||||
|
[120.848454, "o", "i"]
|
||||||
|
[120.924386, "o", "g"]
|
||||||
|
[121.027572, "o", "u"]
|
||||||
|
[121.112001, "o", "r"]
|
||||||
|
[121.189835, "o", "a"]
|
||||||
|
[121.343833, "o", "t"]
|
||||||
|
[121.451598, "o", "i"]
|
||||||
|
[121.483569, "o", "o"]
|
||||||
|
[121.544645, "o", "n"]
|
||||||
|
[121.693602, "o", " "]
|
||||||
|
[121.79612, "o", "f"]
|
||||||
|
[122.761645, "o", "o"]
|
||||||
|
[122.901749, "o", "r"]
|
||||||
|
[123.145145, "o", " "]
|
||||||
|
[123.304789, "o", "n"]
|
||||||
|
[123.372733, "o", "o"]
|
||||||
|
[123.473625, "o", "w"]
|
||||||
|
[124.049661, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[124.065701, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:50 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[130.128615, "o", "#"]
|
||||||
|
[130.692546, "o", " "]
|
||||||
|
[130.93938, "o", "l"]
|
||||||
|
[131.043615, "o", "e"]
|
||||||
|
[131.13983, "o", "s"]
|
||||||
|
[131.555945, "o", "\b\u001b[K"]
|
||||||
|
[131.663469, "o", "t"]
|
||||||
|
[131.723532, "o", "s"]
|
||||||
|
[131.843418, "o", " "]
|
||||||
|
[131.987492, "o", "a"]
|
||||||
|
[132.096224, "o", "d"]
|
||||||
|
[132.25656, "o", "d"]
|
||||||
|
[132.352497, "o", " "]
|
||||||
|
[132.572509, "o", "s"]
|
||||||
|
[132.692485, "o", "o"]
|
||||||
|
[132.796747, "o", "m"]
|
||||||
|
[132.92812, "o", "e"]
|
||||||
|
[133.040381, "o", " "]
|
||||||
|
[133.21634, "o", "c"]
|
||||||
|
[133.268543, "o", "o"]
|
||||||
|
[133.316484, "o", "n"]
|
||||||
|
[133.396408, "o", "f"]
|
||||||
|
[133.496, "o", "i"]
|
||||||
|
[133.584045, "o", "g"]
|
||||||
|
[134.036184, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[134.051459, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:50 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[135.546399, "o", "\u001b[7mtotp add test JBSWY3DPEHPK3PXP\u001b[27m"]
|
||||||
|
[136.875925, "o", "\r\u001b[C\u001b[Ctotp add test JBSWY3DPEHPK3PXP\r\n\u001b[?2004l\r"]
|
||||||
|
[136.889149, "o", "Config not found. Creating new config\r\nCreating /home/ruben/.config/totp/apps.edn\r\n"]
|
||||||
|
[136.890105, "o", "App test not found\r\n"]
|
||||||
|
[136.893221, "o", "App test added or updated.\r\n"]
|
||||||
|
[136.912416, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:50 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[140.795747, "o", "t"]
|
||||||
|
[140.88717, "o", "o"]
|
||||||
|
[141.015232, "o", "t"]
|
||||||
|
[141.139247, "o", "p"]
|
||||||
|
[141.299209, "o", " "]
|
||||||
|
[141.447423, "o", "l"]
|
||||||
|
[141.540391, "o", "i"]
|
||||||
|
[141.563636, "o", "s"]
|
||||||
|
[141.647284, "o", "t"]
|
||||||
|
[142.235768, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[142.248069, "o", "List mode: list\r\n"]
|
||||||
|
[142.248867, "o", "test\r\n"]
|
||||||
|
[142.267845, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:50 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[147.531311, "o", "#"]
|
||||||
|
[147.883753, "o", " "]
|
||||||
|
[148.339198, "o", "n"]
|
||||||
|
[148.407112, "o", "o"]
|
||||||
|
[148.519068, "o", "e"]
|
||||||
|
[148.56398, "o", "w"]
|
||||||
|
[149.175509, "o", "\b\u001b[K"]
|
||||||
|
[149.323071, "o", "\b\u001b[K"]
|
||||||
|
[149.431036, "o", "w"]
|
||||||
|
[149.739772, "o", ","]
|
||||||
|
[149.835045, "o", " "]
|
||||||
|
[150.267209, "o", "l"]
|
||||||
|
[150.395079, "o", "e"]
|
||||||
|
[150.467278, "o", "t"]
|
||||||
|
[150.719059, "o", "'"]
|
||||||
|
[150.931756, "o", "s"]
|
||||||
|
[151.184173, "o", " "]
|
||||||
|
[151.611112, "o", "u"]
|
||||||
|
[151.762964, "o", "s"]
|
||||||
|
[151.844066, "o", "e"]
|
||||||
|
[152.024966, "o", " "]
|
||||||
|
[152.295203, "o", "i"]
|
||||||
|
[152.347119, "o", "t"]
|
||||||
|
[153.263554, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[153.278788, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:51 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[155.303403, "o", "t"]
|
||||||
|
[156.036697, "o", "\b\u001b[K"]
|
||||||
|
[161.713757, "o", "\u001b[7mtotp get test\u001b[27m"]
|
||||||
|
[164.459119, "o", "\r\u001b[C\u001b[Ctotp get test\r\n\u001b[?2004l\r"]
|
||||||
|
[164.47357, "o", "[1759269078109] test -> 538187\r\n"]
|
||||||
|
[164.494047, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:51 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[167.643612, "o", "n"]
|
||||||
|
[167.743908, "o", "i"]
|
||||||
|
[167.907667, "o", "c"]
|
||||||
|
[168.11663, "o", "e"]
|
||||||
|
[168.679846, "o", "\b\u001b[K"]
|
||||||
|
[168.835568, "o", "\b\u001b[K"]
|
||||||
|
[168.987573, "o", "\b\u001b[K"]
|
||||||
|
[169.134704, "o", "\b\u001b[K"]
|
||||||
|
[169.664678, "o", "#"]
|
||||||
|
[169.944556, "o", "n"]
|
||||||
|
[170.036654, "o", "i"]
|
||||||
|
[170.655785, "o", "c"]
|
||||||
|
[170.720607, "o", "c"]
|
||||||
|
[170.871841, "o", "e"]
|
||||||
|
[171.087914, "o", ","]
|
||||||
|
[171.176657, "o", " "]
|
||||||
|
[171.728478, "o", "\b\u001b[K"]
|
||||||
|
[171.880337, "o", "\b\u001b[K"]
|
||||||
|
[172.028366, "o", "\b\u001b[K"]
|
||||||
|
[172.163433, "o", "\b\u001b[K"]
|
||||||
|
[172.307877, "o", "e"]
|
||||||
|
[172.80039, "o", ","]
|
||||||
|
[172.875822, "o", " "]
|
||||||
|
[173.263396, "o", "b"]
|
||||||
|
[173.32745, "o", "u"]
|
||||||
|
[173.592525, "o", "t"]
|
||||||
|
[173.744595, "o", " "]
|
||||||
|
[175.150913, "o", "i"]
|
||||||
|
[175.275145, "o", "t"]
|
||||||
|
[175.370513, "o", " "]
|
||||||
|
[175.680036, "o", "c"]
|
||||||
|
[175.780515, "o", "a"]
|
||||||
|
[175.87158, "o", "n"]
|
||||||
|
[175.971182, "o", " "]
|
||||||
|
[176.163563, "o", "b"]
|
||||||
|
[176.404005, "o", "e"]
|
||||||
|
[176.496397, "o", " "]
|
||||||
|
[176.77961, "o", "b"]
|
||||||
|
[177.07975, "o", "e"]
|
||||||
|
[177.539027, "o", "t"]
|
||||||
|
[177.682551, "o", "t"]
|
||||||
|
[177.827595, "o", "e"]
|
||||||
|
[177.924439, "o", "r"]
|
||||||
|
[178.038649, "o", ","]
|
||||||
|
[178.132454, "o", " "]
|
||||||
|
[178.272441, "o", "l"]
|
||||||
|
[178.351211, "o", "e"]
|
||||||
|
[178.471602, "o", "s"]
|
||||||
|
[178.971113, "o", "\b\u001b[K"]
|
||||||
|
[179.0676, "o", "t"]
|
||||||
|
[179.123609, "o", "s"]
|
||||||
|
[179.247413, "o", " "]
|
||||||
|
[180.082803, "o", "u"]
|
||||||
|
[180.2324, "o", "p"]
|
||||||
|
[180.394597, "o", "d"]
|
||||||
|
[180.482853, "o", "a"]
|
||||||
|
[180.648252, "o", "t"]
|
||||||
|
[180.723541, "o", "e"]
|
||||||
|
[181.03104, "o", " "]
|
||||||
|
[182.038726, "o", "\b\u001b[K"]
|
||||||
|
[182.669551, "o", "\b\u001b[K"]
|
||||||
|
[182.708688, "o", "\b\u001b[K"]
|
||||||
|
[182.748425, "o", "\b\u001b[K"]
|
||||||
|
[182.788279, "o", "\b\u001b[K"]
|
||||||
|
[182.829023, "o", "\b\u001b[K"]
|
||||||
|
[182.868831, "o", "\b\u001b[K"]
|
||||||
|
[182.908891, "o", "\b\u001b[K"]
|
||||||
|
[182.948819, "o", "\b\u001b[K"]
|
||||||
|
[183.134335, "o", "\b\u001b[K"]
|
||||||
|
[183.31039, "o", "\b\u001b[K"]
|
||||||
|
[183.462279, "o", "\b\u001b[K"]
|
||||||
|
[183.626415, "o", "\b\u001b[K"]
|
||||||
|
[184.226378, "o", " "]
|
||||||
|
[184.548189, "o", "w"]
|
||||||
|
[184.64351, "o", "i"]
|
||||||
|
[184.842896, "o", "t"]
|
||||||
|
[184.95423, "o", "h"]
|
||||||
|
[185.182882, "o", " "]
|
||||||
|
[185.410352, "o", "a"]
|
||||||
|
[185.531177, "o", "u"]
|
||||||
|
[185.626453, "o", "t"]
|
||||||
|
[185.71036, "o", "o"]
|
||||||
|
[185.798217, "o", " "]
|
||||||
|
[185.939366, "o", "u"]
|
||||||
|
[186.087955, "o", "p"]
|
||||||
|
[186.264704, "o", "d"]
|
||||||
|
[186.311424, "o", "a"]
|
||||||
|
[186.483026, "o", "t"]
|
||||||
|
[186.555381, "o", "e"]
|
||||||
|
[186.888118, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[186.904273, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:51 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[194.832903, "o", "\u001b[7mtotp get test -c -s ascii-basic\u001b[27m"]
|
||||||
|
[195.895065, "o", "\r\u001b[C\u001b[Ctotp get test -c -s ascii-basic\r\n\u001b[?2004l\r"]
|
||||||
|
[195.907922, "o", "\r\n <Generating continuosly, press enter to stop>\r\n\r\n"]
|
||||||
|
[195.908159, "o", "Refresing in 10 seconds\r\n\r\n\r\n"]
|
||||||
|
[195.908736, "o", "[1759269109544] test -> 993001\r\n\r\n"]
|
||||||
|
[195.910021, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m###################################\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 20/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[196.913521, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m#####################################\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 21/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[197.914821, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m#######################################\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 22/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[198.916125, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m########################################\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 23/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[199.917646, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m##########################################\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 24/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[200.918974, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m############################################\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 25/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[201.920305, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m##############################################\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 26/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[202.921624, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m###############################################\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 27/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[203.922865, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m#################################################\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 28/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[204.924224, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m###################################################\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 29/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[205.925493, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m####################################################\u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 30/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[206.365098, "o", "\r\n\r\n"]
|
||||||
|
[206.365278, "o", "[1759269120001] test -> 539013\r\n\r\n"]
|
||||||
|
[206.365606, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m##\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 1/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[206.910784, "o", "\r\u001b[K"]
|
||||||
|
[207.367214, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m####\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 2/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[208.368552, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m######\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 3/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[209.369936, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[39m[\u001b[m\u001b[m\u001b[49m\u001b[39m#######\u001b[m\u001b[m\u001b[49m\u001b[39m \u001b[m\u001b[m\u001b[49m\u001b[39m]\u001b[m\u001b[m\u001b[49m\u001b[39m 4/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[209.410133, "o", "\r\n"]
|
||||||
|
[209.430893, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:52 \u001b[0m\u001b[38;2;60;56;54m \u001b[0mtook \u001b[1;33m13s\u001b[0m \r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[210.699351, "o", "#"]
|
||||||
|
[211.038494, "o", " "]
|
||||||
|
[211.309807, "o", "s"]
|
||||||
|
[211.882298, "o", "\b\u001b[K"]
|
||||||
|
[211.949712, "o", "a"]
|
||||||
|
[211.99763, "o", "s"]
|
||||||
|
[212.105601, "o", " "]
|
||||||
|
[212.410208, "o", "y"]
|
||||||
|
[212.489736, "o", "o"]
|
||||||
|
[212.653977, "o", "u"]
|
||||||
|
[212.993745, "o", " "]
|
||||||
|
[213.370889, "o", "c"]
|
||||||
|
[213.470001, "o", "a"]
|
||||||
|
[213.53479, "o", "n"]
|
||||||
|
[213.663549, "o", " "]
|
||||||
|
[213.763578, "o", "s"]
|
||||||
|
[214.043568, "o", "e"]
|
||||||
|
[214.173681, "o", "e"]
|
||||||
|
[214.466255, "o", ","]
|
||||||
|
[214.549634, "o", " "]
|
||||||
|
[216.30996, "o", "t"]
|
||||||
|
[216.461652, "o", "e"]
|
||||||
|
[216.493704, "o", "h"]
|
||||||
|
[217.06972, "o", "\b\u001b[K"]
|
||||||
|
[217.205766, "o", "\b\u001b[K"]
|
||||||
|
[217.413748, "o", "h"]
|
||||||
|
[217.481597, "o", "e"]
|
||||||
|
[217.594439, "o", "r"]
|
||||||
|
[217.698256, "o", "e"]
|
||||||
|
[217.823511, "o", " "]
|
||||||
|
[217.934876, "o", "a"]
|
||||||
|
[218.234185, "o", "r"]
|
||||||
|
[218.325626, "o", "e"]
|
||||||
|
[218.521534, "o", " "]
|
||||||
|
[220.314138, "o", "s"]
|
||||||
|
[220.377731, "o", "o"]
|
||||||
|
[220.585578, "o", "m"]
|
||||||
|
[220.733803, "o", "e"]
|
||||||
|
[220.781393, "o", " "]
|
||||||
|
[221.510396, "o", "s"]
|
||||||
|
[221.665754, "o", "t"]
|
||||||
|
[221.857486, "o", "y"]
|
||||||
|
[222.083464, "o", "l"]
|
||||||
|
[222.206997, "o", "e"]
|
||||||
|
[222.262264, "o", "s"]
|
||||||
|
[222.330128, "o", " "]
|
||||||
|
[222.497639, "o", "f"]
|
||||||
|
[222.58273, "o", "o"]
|
||||||
|
[222.686721, "o", "r"]
|
||||||
|
[222.821483, "o", " "]
|
||||||
|
[222.913548, "o", "t"]
|
||||||
|
[223.033543, "o", "h"]
|
||||||
|
[223.149439, "o", "e"]
|
||||||
|
[223.20627, "o", " "]
|
||||||
|
[223.335623, "o", "p"]
|
||||||
|
[223.418267, "o", "r"]
|
||||||
|
[223.534662, "o", "o"]
|
||||||
|
[223.654181, "o", "g"]
|
||||||
|
[223.838228, "o", "r"]
|
||||||
|
[223.90657, "o", "e"]
|
||||||
|
[223.965747, "o", "s"]
|
||||||
|
[224.122669, "o", "s"]
|
||||||
|
[224.269633, "o", " "]
|
||||||
|
[224.434395, "o", "b"]
|
||||||
|
[224.574579, "o", "a"]
|
||||||
|
[224.64739, "o", "r"]
|
||||||
|
[225.626113, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[225.641031, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:52 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[233.457421, "o", "#"]
|
||||||
|
[235.829555, "o", " "]
|
||||||
|
[236.685245, "o", "l"]
|
||||||
|
[238.381942, "o", "e"]
|
||||||
|
[238.613414, "o", "t"]
|
||||||
|
[239.501198, "o", "'"]
|
||||||
|
[239.585226, "o", "s"]
|
||||||
|
[239.701157, "o", " "]
|
||||||
|
[239.879208, "o", "d"]
|
||||||
|
[240.37912, "o", "e"]
|
||||||
|
[240.5226, "o", "l"]
|
||||||
|
[240.650743, "o", "e"]
|
||||||
|
[240.789392, "o", "t"]
|
||||||
|
[240.869225, "o", "e"]
|
||||||
|
[240.973015, "o", " "]
|
||||||
|
[241.305866, "o", "t"]
|
||||||
|
[241.416959, "o", "h"]
|
||||||
|
[241.577133, "o", "e"]
|
||||||
|
[241.69785, "o", " "]
|
||||||
|
[242.019033, "o", "c"]
|
||||||
|
[242.033506, "o", "o"]
|
||||||
|
[242.137762, "o", "n"]
|
||||||
|
[242.209209, "o", "f"]
|
||||||
|
[242.341133, "o", "i"]
|
||||||
|
[242.433165, "o", "g"]
|
||||||
|
[242.573055, "o", "u"]
|
||||||
|
[242.641041, "o", "r"]
|
||||||
|
[242.713105, "o", "a"]
|
||||||
|
[242.83762, "o", "t"]
|
||||||
|
[242.917079, "o", "i"]
|
||||||
|
[242.969032, "o", "o"]
|
||||||
|
[243.013048, "o", "n"]
|
||||||
|
[243.138504, "o", " "]
|
||||||
|
[243.473234, "o", "\""]
|
||||||
|
[243.685267, "o", "t"]
|
||||||
|
[243.760932, "o", "e"]
|
||||||
|
[243.869004, "o", "s"]
|
||||||
|
[243.973005, "o", "t"]
|
||||||
|
[244.373051, "o", "\""]
|
||||||
|
[244.654483, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[244.669815, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:52 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[251.917944, "o", "\u001b[7mtotp delete test\u001b[27m"]
|
||||||
|
[252.989446, "o", "\r\u001b[C\u001b[Ctotp delete test\r\n\u001b[?2004l\r"]
|
||||||
|
[253.02292, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:52 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[255.301036, "o", "t"]
|
||||||
|
[255.504806, "o", "o"]
|
||||||
|
[255.65279, "o", "t"]
|
||||||
|
[255.800825, "o", "p"]
|
||||||
|
[257.001238, "o", " "]
|
||||||
|
[257.224701, "o", "l"]
|
||||||
|
[257.304734, "o", "i"]
|
||||||
|
[257.324649, "o", "s"]
|
||||||
|
[257.436758, "o", "t"]
|
||||||
|
[257.757304, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[257.769018, "o", "List mode: list\r\n"]
|
||||||
|
[257.788948, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:52 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[258.944879, "o", "#"]
|
||||||
|
[259.517298, "o", " "]
|
||||||
|
[259.784678, "o", "n"]
|
||||||
|
[259.840851, "o", "o"]
|
||||||
|
[259.932589, "o", " "]
|
||||||
|
[260.278845, "o", "c"]
|
||||||
|
[260.3467, "o", "o"]
|
||||||
|
[260.425825, "o", "n"]
|
||||||
|
[260.493876, "o", "f"]
|
||||||
|
[260.598613, "o", "i"]
|
||||||
|
[260.677442, "o", "g"]
|
||||||
|
[260.768652, "o", "u"]
|
||||||
|
[260.880472, "o", "r"]
|
||||||
|
[260.953382, "o", "a"]
|
||||||
|
[261.060749, "o", "t"]
|
||||||
|
[261.112634, "o", "i"]
|
||||||
|
[261.180594, "o", "o"]
|
||||||
|
[261.204656, "o", "n"]
|
||||||
|
[261.348611, "o", "s"]
|
||||||
|
[261.554583, "o", " "]
|
||||||
|
[261.789793, "o", "s"]
|
||||||
|
[261.861763, "o", "o"]
|
||||||
|
[262.198226, "o", " "]
|
||||||
|
[262.341632, "o", "f"]
|
||||||
|
[262.492809, "o", "a"]
|
||||||
|
[262.593517, "o", "r"]
|
||||||
|
[263.625065, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[263.640423, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:52 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[267.84483, "o", "w"]
|
||||||
|
[268.512968, "o", "\b\u001b[K"]
|
||||||
|
[268.896408, "o", "#"]
|
||||||
|
[269.113218, "o", " "]
|
||||||
|
[269.372569, "o", "w"]
|
||||||
|
[269.500297, "o", "e"]
|
||||||
|
[269.661237, "o", " "]
|
||||||
|
[269.854302, "o", "c"]
|
||||||
|
[269.989274, "o", "a"]
|
||||||
|
[270.078315, "o", "n"]
|
||||||
|
[270.152714, "o", " "]
|
||||||
|
[270.309658, "o", "i"]
|
||||||
|
[270.485438, "o", "m"]
|
||||||
|
[270.570015, "o", "p"]
|
||||||
|
[270.596809, "o", "o"]
|
||||||
|
[270.641587, "o", "r"]
|
||||||
|
[270.832613, "o", "t"]
|
||||||
|
[270.952398, "o", " "]
|
||||||
|
[271.408861, "o", "f"]
|
||||||
|
[271.621126, "o", "r"]
|
||||||
|
[271.696271, "o", "o"]
|
||||||
|
[271.752437, "o", "m"]
|
||||||
|
[271.85841, "o", " "]
|
||||||
|
[272.000574, "o", "a"]
|
||||||
|
[272.124981, "o", " "]
|
||||||
|
[272.305481, "o", "d"]
|
||||||
|
[272.938453, "o", "\b\u001b[K"]
|
||||||
|
[273.452832, "o", "d"]
|
||||||
|
[273.613687, "o", "e"]
|
||||||
|
[273.833281, "o", "c"]
|
||||||
|
[274.154276, "o", "o"]
|
||||||
|
[274.253164, "o", "d"]
|
||||||
|
[274.432922, "o", "e"]
|
||||||
|
[274.588231, "o", "d"]
|
||||||
|
[274.733447, "o", " "]
|
||||||
|
[274.981022, "o", "Q"]
|
||||||
|
[275.050288, "o", "R"]
|
||||||
|
[275.362007, "o", " "]
|
||||||
|
[277.496889, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[277.512098, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:53 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[285.370066, "o", "\u001b[7mtotp import test2 \"otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example\"\u001b[27m"]
|
||||||
|
[287.308788, "o", "\r\u001b[C\u001b[Ctotp import test2 \"otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example\"\r\n\u001b[?2004l\r"]
|
||||||
|
[287.322431, "o", "App test2 not found\r\n"]
|
||||||
|
[287.326283, "o", "Import successful\r\n"]
|
||||||
|
[287.346185, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:53 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[288.769428, "o", "#"]
|
||||||
|
[289.141148, "o", " "]
|
||||||
|
[290.424621, "o", "b"]
|
||||||
|
[290.725468, "o", "e"]
|
||||||
|
[290.868228, "o", " "]
|
||||||
|
[291.173075, "o", "c"]
|
||||||
|
[291.297181, "o", "a"]
|
||||||
|
[291.426014, "o", "r"]
|
||||||
|
[291.548835, "o", "e"]
|
||||||
|
[291.900446, "o", "f"]
|
||||||
|
[292.025023, "o", "u"]
|
||||||
|
[292.278039, "o", "l"]
|
||||||
|
[292.432302, "o", "l"]
|
||||||
|
[292.548875, "o", " "]
|
||||||
|
[293.284385, "o", "\b\u001b[K"]
|
||||||
|
[293.531889, "o", ","]
|
||||||
|
[293.631761, "o", " "]
|
||||||
|
[294.360849, "o", "e"]
|
||||||
|
[295.140282, "o", "\b\u001b[K"]
|
||||||
|
[295.475851, "o", "u"]
|
||||||
|
[295.54373, "o", "s"]
|
||||||
|
[295.612843, "o", "e"]
|
||||||
|
[295.760867, "o", " "]
|
||||||
|
[297.521913, "o", "\""]
|
||||||
|
[298.656149, "o", " "]
|
||||||
|
[300.597656, "o", "w"]
|
||||||
|
[300.749634, "o", "h"]
|
||||||
|
[300.892144, "o", "e"]
|
||||||
|
[301.180181, "o", "n"]
|
||||||
|
[301.296973, "o", " "]
|
||||||
|
[302.500014, "o", "p"]
|
||||||
|
[302.615858, "o", "a"]
|
||||||
|
[302.67969, "o", "s"]
|
||||||
|
[302.849466, "o", "s"]
|
||||||
|
[302.961937, "o", "i"]
|
||||||
|
[303.101489, "o", "n"]
|
||||||
|
[303.180088, "o", "g"]
|
||||||
|
[303.35366, "o", " "]
|
||||||
|
[303.48116, "o", "a"]
|
||||||
|
[303.604602, "o", " "]
|
||||||
|
[303.816902, "o", "u"]
|
||||||
|
[303.976617, "o", "r"]
|
||||||
|
[304.101316, "o", "l"]
|
||||||
|
[307.059903, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[307.075511, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:53 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[308.748767, "o", "w"]
|
||||||
|
[308.912618, "o", "e"]
|
||||||
|
[309.091714, "o", " "]
|
||||||
|
[309.255696, "o", "c"]
|
||||||
|
[309.36445, "o", "a"]
|
||||||
|
[309.464001, "o", "n"]
|
||||||
|
[309.528542, "o", " "]
|
||||||
|
[309.688527, "o", "u"]
|
||||||
|
[309.731301, "o", "s"]
|
||||||
|
[309.829634, "o", "e"]
|
||||||
|
[310.060886, "o", " "]
|
||||||
|
[310.572134, "o", "t"]
|
||||||
|
[310.651382, "o", "h"]
|
||||||
|
[310.755376, "o", "e"]
|
||||||
|
[310.873195, "o", " "]
|
||||||
|
[311.636149, "o", "i"]
|
||||||
|
[311.815418, "o", "m"]
|
||||||
|
[311.884555, "o", "p"]
|
||||||
|
[311.944949, "o", "o"]
|
||||||
|
[311.984584, "o", "r"]
|
||||||
|
[312.220074, "o", "t"]
|
||||||
|
[312.283313, "o", "e"]
|
||||||
|
[312.508213, "o", " "]
|
||||||
|
[312.600614, "o", "c"]
|
||||||
|
[312.953066, "o", "\b\u001b[K"]
|
||||||
|
[313.088559, "o", "\b\u001b[K"]
|
||||||
|
[313.281026, "o", "d"]
|
||||||
|
[313.459927, "o", " "]
|
||||||
|
[313.621497, "o", "c"]
|
||||||
|
[313.676178, "o", "o"]
|
||||||
|
[313.716444, "o", "n"]
|
||||||
|
[313.807705, "o", "f"]
|
||||||
|
[313.90908, "o", "i"]
|
||||||
|
[313.99701, "o", "g"]
|
||||||
|
[314.111396, "o", "u"]
|
||||||
|
[314.208486, "o", "r"]
|
||||||
|
[314.277257, "o", "a"]
|
||||||
|
[314.407495, "o", "t"]
|
||||||
|
[314.47123, "o", "i"]
|
||||||
|
[314.527248, "o", "o"]
|
||||||
|
[314.584277, "o", "n"]
|
||||||
|
[315.059961, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[315.190478, "o", "we: no se encontró la orden\r\n"]
|
||||||
|
[315.214549, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:53 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;204;36;29m\u001b[0m "]
|
||||||
|
[315.32342, "o", "t"]
|
||||||
|
[315.379208, "o", "o"]
|
||||||
|
[315.556049, "o", "t"]
|
||||||
|
[315.631347, "o", "p"]
|
||||||
|
[315.823791, "o", " "]
|
||||||
|
[316.115847, "o", "l"]
|
||||||
|
[316.167385, "o", "i"]
|
||||||
|
[316.212386, "o", "s"]
|
||||||
|
[316.323384, "o", "t"]
|
||||||
|
[316.652722, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[316.66479, "o", "List mode: list\r\n"]
|
||||||
|
[316.665415, "o", "test2\r\n"]
|
||||||
|
[316.684175, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:53 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[323.48764, "o", "\u001b[7mtotp get test2 -c\u001b[27m"]
|
||||||
|
[324.439742, "o", "\r\u001b[C\u001b[Ctotp get test2 -c\r\n\u001b[?2004l\r"]
|
||||||
|
[324.452596, "o", "\r\n <Generating continuosly, press enter to stop>\r\n\r\n"]
|
||||||
|
[324.452846, "o", "Refresing in 1 seconds\r\n\r\n\r\n"]
|
||||||
|
[324.453445, "o", "[1759269238089] test2 -> 957725\r\n\r\n"]
|
||||||
|
[324.454681, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉\u001b[m\u001b[m\u001b[49m\u001b[39m 29/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[325.456238, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[39m 30/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[326.364838, "o", "\r\n\r\n"]
|
||||||
|
[326.364948, "o", "[1759269240001] test2 -> 227087\r\n\r\n"]
|
||||||
|
[326.365295, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[39m 1/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[326.45472, "o", "\r\u001b[K"]
|
||||||
|
[327.366772, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[39m 2/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[328.368886, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[39m 3/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[329.370693, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[39m 4/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[330.372132, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[39m 5/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[331.373589, "o", "\u001b[25l\r\u001b[49m\u001b[39mNext TOTP: \u001b[m\u001b[m\u001b[49m\u001b[97m▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[93m▉\u001b[m\u001b[m\u001b[49m\u001b[90m▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉\u001b[m\u001b[m\u001b[49m\u001b[39m 6/30\u001b[m\u001b[m\u001b[K\u001b[25h"]
|
||||||
|
[331.57506, "o", "\r\n"]
|
||||||
|
[331.594921, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:54 \u001b[0m\u001b[38;2;60;56;54m \u001b[0mtook \u001b[1;33m7s\u001b[0m \r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[332.515102, "o", "#"]
|
||||||
|
[333.135521, "o", " "]
|
||||||
|
[333.803007, "o", "h"]
|
||||||
|
[333.882813, "o", "a"]
|
||||||
|
[334.21893, "o", "v"]
|
||||||
|
[334.403614, "o", "e"]
|
||||||
|
[334.52687, "o", " "]
|
||||||
|
[334.871466, "o", "f"]
|
||||||
|
[334.951931, "o", "u"]
|
||||||
|
[335.148189, "o", "n"]
|
||||||
|
[335.488484, "o", "!"]
|
||||||
|
[336.103143, "o", "\r\n\u001b[?2004l\r"]
|
||||||
|
[336.118171, "o", "\u001b[?2004h\r\n\u001b[38;2;214;93;14m\u001b[48;2;214;93;14;38;2;251;241;199m ruben\u001b[48;2;215;153;33;38;2;214;93;14m\u001b[38;2;251;241;199m …/clj-totp \u001b[48;2;104;157;106;38;2;215;153;33m\u001b[38;2;251;241;199m main ? \u001b[48;2;69;133;136;38;2;104;157;106m\u001b[38;2;251;241;199m v21.0.8 \u001b[48;2;102;92;84;38;2;69;133;136m\u001b[48;2;60;56;54;38;2;102;92;84m\u001b[38;2;251;241;199m 23:54 \u001b[0m\u001b[38;2;60;56;54m \u001b[0m\r\n\u001b[1;38;2;152;151;26m\u001b[0m "]
|
||||||
|
[336.90336, "o", "\u001b[?2004l\r\r\nexit\r\n"]
|
||||||
BIN
use_example.gif
Normal file
BIN
use_example.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
Reference in New Issue
Block a user