Compare commits

17 Commits

122 changed files with 1499 additions and 103 deletions

View File

@@ -17,6 +17,8 @@ You can read more about the algorithm here:
## How to use ## How to use
![Use example](use_example.gif)
First, you must have installed a Java Runtime Environment. Check https://adoptium.net/es if you are First, you must have installed a Java Runtime Environment. Check https://adoptium.net/es if you are
unsure how to install. unsure how to install.
@@ -145,11 +147,16 @@ clj-totp.sh import <alias> "<url>"
- [x] Show several OTPs at once - [x] Show several OTPs at once
### v1.2 ### v1.2
- [x] Show progress bar
- [x] Styles for progress bar
- [x] Native compilation script corrections
### v2
- [ ] REST API - [ ] REST API
- [ ] User management - [ ] User management
- [ ] Robust BD backend (H2, datomic, or similar) - [ ] Robust BD backend (H2, datomic, or similar)
### v1.3 ### v3
- [ ] Simple web connected to REST API - [ ] Simple web connected to REST API

122
build.clj
View File

@@ -1,37 +1,101 @@
(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]))
(def lib 'es.rcorral/clj-totp) (def lib-group "es.rcorral")
(def version (format "1.1.%s" (b/git-count-revs nil))) (def artifact-prefix "clj-totp")
(def target-dir "target") (def curr-version (format "2.0.%s" (b/git-count-revs nil)))
(def class-dir (str target-dir "/classes"))
(def uber-file (format "target/%s-%s-standalone.jar" (name lib) version))
;; delay to defer side effects (artifact downloads) ;; Builds artifact's full descriptor for each subproject
(def basis (delay (b/create-basis {:project "deps.edn"}))) (defn lib [subproj]
(symbol (str lib-group "/" artifact-prefix "-" subproj )))
(defn clean [_] ;; Basis for each subproject, using their own deps.edn
(b/delete {:path "target"})) (defn basis [subproj]
;(b/create-basis {:project (str subproj "/deps.edn")}))
(delay (b/create-basis {:project (str "projects/" subproj "/deps.edn")})))
(defn compile-java [_] ;; Target dir for each subproject
(b/javac {:src-dirs ["java"] (defn target-dir [subproj]
:class-dir class-dir (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 "projects/" subproj "/java")]
(if (.exists (io/file java-dir))
(do
(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"]}))
(println "No java code for" subproj ", skipping compilation"))))
;; Create a jar file
(defn jar
"Build a simple jar file, with no dependencies included."
[{:keys [subproj version]}]
(let [real-version (if version version curr-version)
target-dir (target-dir subproj)
class-dir (class-dir subproj)
src-dir (str "projects/" subproj "/src")
resources-dir (str "projects/" subproj "/resources")
basis (basis subproj)
jar-file (jar-file subproj real-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 :basis @basis
:javac-opts ["-source" "11" "--target" "11" "-proc:none"]})) :jar-file jar-file
:lib (lib subproj)
:version real-version})
(println "Generated jar file:" jar-file)))
;; Create an uber jar, with all dependencies inside
(defn uber
"Build a uberjar with all dependencies included"
[{:keys [subproj version main-ns]}]
(let [real-version (if version version curr-version)target-dir (target-dir subproj)
basis (basis subproj)
class-dir (class-dir subproj)
src-dir (str "projects/" subproj "/src")
resources-dir (str "projects/" subproj "/resources")
uber-file (jar-file subproj real-version :uber)]
(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)))
#_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]}
(defn uber [_]
(clean nil)
(b/copy-dir {:src-dirs ["src"]
:target-dir class-dir})
(b/copy-file {:src "resources/clj-totp.sh"
:target "target/clj-totp.sh"})
(compile-java nil)
(b/compile-clj {:basis @basis
:ns-compile '[totp.app]
:class-dir class-dir})
(b/uber {:class-dir class-dir
:uber-file uber-file
:basis @basis
:main 'totp.app}))

View File

@@ -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>

View File

@@ -1,19 +1,9 @@
{:paths ["src" "resources" "target/classes"] {: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"}
mvxcvi/alphabase {:mvn/version "3.0.185"} ;; https://github.com/greglook/alphabase
cli-matic/cli-matic {:mvn/version "0.5.4"} ;; https://github.com/l3nz/cli-matic
;; 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
;; Native image (GraalVM) ;; 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/ com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}}
;; Protobuf for java
com.google.protobuf/protobuf-java {:mvn/version "3.25.8"}}
:aliases {;; Execute the app :aliases {;; Execute the app. Tutorial: https://shagunagrawal.me/posts/setup-clojure-with-graalvm-for-native-image/
:run {:main-opts ["-m" "totp.app"]} :run {:main-opts ["-m" "totp.app"]}
;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha ;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
@@ -23,5 +13,46 @@
;; 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}}} :ns-default build}
;; COMMON ALIASES FOR ALL PROJECTS
:root/run-x {:exec-fn -main}
:root/extra-paths [:totp.core/extra-paths
:totp.cli/extra-paths
:totp.web/extra-paths]
:root/all {:extra-paths ["src" "resources"
:root/extra-paths]}
:root/test {:extra-paths ["test"]
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
:main-opts ["-m" "kaocha.runner"]}
:boot/build {:extra-paths ["build"]
:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
:ns-default build
;:exec-fn ci
;:exec-args {:app-alias :com.example.core}
}
:totp.core/extra-paths ["projects/core/src"
"projects/core/test"
"projects/core/java"
"projects/core/resources"]
:totp.core {:ns-default totp.core
:main-opts ["-m" "totp.core"]
:extra-deps {projects/core {:local/root "projects/core"}}
:exec-args {:dirs ["projects/core"]}}
:totp.cli/extra-paths ["projects/app/src"]
:totp.cli {:ns-default totp.cli
:main-opts ["-m" "totp.cli"]
:extra-deps {;; does not use parts/grugstack {:local/root "parts"}
projects/cli {:local/root "projects/cli"}}
:exec-args {:dirs ["projects/cli"]}}
:totp.web/extra-paths ["projects/web/src"]
}}

33
native.cmd Normal file
View 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

View File

@@ -8,8 +8,23 @@ clojure -T:build uber
UBERJAR=$(realpath --relative-to=target target/clj-totp-*-standalone.jar) UBERJAR=$(realpath --relative-to=target target/clj-totp-*-standalone.jar)
echo "Creating native image" 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 $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\
--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'
echo "Executable created on target/$BIN_FILE" echo "Executable created on target/$BIN_FILE"
cp target/$BIN_FILE ~/bin cp target/$BIN_FILE ~/bin
echo "Copied to ~/bin/$BIN_FILE" echo "Copied to ~/bin/$BIN_FILE"
echo "Compress executable for distribution"
xz -fv target/$BIN_FILE

16
projects/cli/deps.edn Executable file
View File

@@ -0,0 +1,16 @@
{:paths ["src" "resources" "target/classes"]
:deps {clj-totp/core {:local/root "projects/core"}
org.clojure/clojure {:mvn/version "1.12.1"}
io.github.clojure/tools.build {:mvn/version "0.10.10"}
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"}}
: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"]}}}

View File

@@ -4,33 +4,63 @@
[totp.data :refer :all] [totp.data :refer :all]
[cli-matic.core :refer [run-cmd]] [cli-matic.core :refer [run-cmd]]
[clojure.pprint :as pp] [clojure.pprint :as pp]
[clojure.string :as str]) [clojure.string :as str]
[progress.determinate :as pd])
(:import [java.util TimerTask Timer]) (:import [java.util TimerTask Timer])
(:gen-class)) (: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 (defn- print-confinuous
([secret] (print-confinuous secret "sha1" 6 30)) ([secret] (print-confinuous secret "sha1" 6 30 true DEFAULT_BAR_STYLE))
([secret algorithm digits period] ([secret algorithm digits period bar bar-style]
(let [step-millis (* 1000 period) (let [step-millis (* 1000 period)
now (System/currentTimeMillis) now (System/currentTimeMillis)
delay (int (- step-millis (rem now step-millis))) delay (int (- step-millis (rem now step-millis)))
fn-show (fn [s] (println (format "[%d] %s" (System/currentTimeMillis) (get-otp s algorithm digits period)))) 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] [] task (proxy [TimerTask] []
(run [] (fn-show secret)))] (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 "\n <Generating continuosly, press enter to stop>\n")
;; (println "Now:" now ", Delay:" delay ", Next execution: " (+ now delay)) ;; (println "Now:" now ", Delay:" delay ", Next execution: " (+ now delay))
(println "Refresing in" (int (/ delay 1000)) "seconds") (println "Refresing in" delay-sec "seconds")
(fn-show secret) (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))) (. (new Timer) (scheduleAtFixedRate task delay step-millis)))
(read-line))) ;; Waits for a key press ;; Waits for a key press
(read-line)))
(defn cmd-generate (defn cmd-generate
[& {:keys [secret continuous algorithm digits period]}] [& {:keys [secret continuous algorithm digits period bar bar-style]}]
;;(pp/pprint opts) ;;(pp/pprint opts)
(if continuous (if continuous
(print-confinuous secret algorithm digits period) (print-confinuous secret algorithm digits period bar bar-style)
(println (get-otp secret algorithm digits period)))) (println (get-otp secret algorithm digits period))))
@@ -49,28 +79,45 @@
(let [step-millis (* 1000 period) (let [step-millis (* 1000 period)
now (System/currentTimeMillis) now (System/currentTimeMillis)
delay (int (- step-millis (rem now step-millis))) delay (int (- step-millis (rem now step-millis)))
delay-sec (int (/ delay 1000))
fn-show (fn [s] fn-show (fn [s]
(println "\n")
(dorun (map print-app s)) (dorun (map print-app s))
(println "")) ;; Separate each (println) ;; Separate each
)
task (proxy [TimerTask] [] task (proxy [TimerTask] []
(run [] (fn-show apps)))] (run [] (fn-show apps)))]
(println "\n <Generating continuosly, press enter to stop>\n") (println "\n <Generating continuosly, press enter to stop>\n")
;; (println "Now:" now ", Delay:" delay ", Next execution: " (+ now delay)) ;; (println "Now:" now ", Delay:" delay ", Next execution: " (+ now delay))
(println "Refresing in" (int (/ delay 1000)) "seconds") (println "Refresing in" delay-sec "seconds")
(fn-show apps) (fn-show apps)
;; Now, start the tasks
(. (new Timer) (scheduleAtFixedRate task delay step-millis))) (. (new Timer) (scheduleAtFixedRate task delay step-millis)))
(read-line))) ;; Waits for a key press )) ;; Waits for a key press
(defn cmd-get-multi (defn cmd-get-multi
[& {:keys [continuous _arguments]}] [& {:keys [continuous bar bar-style _arguments]}]
;(pp/pprint opts) ;(pp/pprint opts)
(with-config (with-config
(let [apps (filter some? #_{:clj-kondo/ignore [:unresolved-symbol]} (let [apps (filter some? #_{:clj-kondo/ignore [:unresolved-symbol]}
(map #(get-app cfg %) _arguments))] (map #(get-app cfg %) _arguments))]
;(println "found apps: " apps) ;(println "found apps: " apps)
(if continuous (if continuous
(print-app-continuous 30 apps) (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)))))) (dorun (map #(print-app %) apps))))))
@@ -153,7 +200,7 @@
(def cli-options (def cli-options
{:app {:command "totp" {:app {:command "totp"
:version "1.1" :version "1.2"
:description ["Generate a TOTP"]} :description ["Generate a TOTP"]}
:commands [;; Generate a TOTP with given params :commands [;; Generate a TOTP with given params
@@ -182,7 +229,15 @@
{:option "period" :short "p" {:option "period" :short "p"
:as "Validity time in seconds" :as "Validity time in seconds"
:type :int :type :int
:default 30}] :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} :runs cmd-generate}
;; Generate a TOTP for a configured app ;; Generate a TOTP for a configured app
{:command "get" :short "g" {:command "get" :short "g"
@@ -198,7 +253,15 @@
{:option "continuous" :short "c" {:option "continuous" :short "c"
:as "Contiuous mode" :as "Contiuous mode"
:type :with-flag :type :with-flag
:default false}] :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} :runs cmd-get-multi}
;; Check and init your config file ;; Check and init your config file
{:command "config" :short "c" {:command "config" :short "c"

1
projects/cli/tests.edn Normal file
View File

@@ -0,0 +1 @@
#kaocha/v1 {}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
-m
kaocha.runner

14
projects/core/deps.edn Executable file
View File

@@ -0,0 +1,14 @@
{:paths ["src" "resources" "target/classes"]
:deps {org.clojure/clojure {:mvn/version "1.12.1"}
io.github.clojure/tools.build {:mvn/version "0.10.10"}
mvxcvi/alphabase {:mvn/version "3.0.185"} ;; https://github.com/greglook/alphabase
cli-matic/cli-matic {:mvn/version "0.5.4"} ;; https://github.com/l3nz/cli-matic
;; Protobuf for java
com.google.protobuf/protobuf-java {:mvn/version "3.25.8"}
}
: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"]}}}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,101 @@
(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)))

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More