Compare commits
22 Commits
main
...
76510be028
| Author | SHA1 | Date | |
|---|---|---|---|
| 76510be028 | |||
| a1fec08cc4 | |||
| 556cc85cde | |||
| 2ebac1676f | |||
| b6749bdb29 | |||
| 1b141173cc | |||
| 06174de597 | |||
| e6523e0a7b | |||
| 78da3c37c0 | |||
| 3d305a0d70 | |||
| 6166e930fe | |||
| 5edbfa4ce4 | |||
| 1071d9e5ee | |||
| 017291f784 | |||
| 17a7a09ab0 | |||
| 3a6fd107c0 | |||
| 4c31950a88 | |||
| aa71cb1d76 | |||
| c746675045 | |||
| 4052995ba8 | |||
| 44f48fced8 | |||
| c78e89a94b |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
/.clj-kondo/
|
/.clj-kondo/
|
||||||
/.cpcache/
|
/**/.cpcache/
|
||||||
/.lsp/
|
/.lsp/
|
||||||
/target/
|
/target/
|
||||||
.nrepl-port
|
.nrepl-port
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ clj-totp.sh import <alias> "<url>"
|
|||||||
- [x] Native compilation script corrections
|
- [x] Native compilation script corrections
|
||||||
|
|
||||||
### v2
|
### 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)
|
||||||
@@ -176,14 +177,14 @@ The first step is to install Java JDK, version 11 or newer (version 21 recommend
|
|||||||
|
|
||||||
To execute manually the main function, simple use the `:run` alias:
|
To execute manually the main function, simple use the `:run` alias:
|
||||||
|
|
||||||
```clojure
|
```bash
|
||||||
clojure -M:run <commands and parameters>
|
clojure -M:run/cli <commands and parameters>
|
||||||
```
|
```
|
||||||
|
|
||||||
To build the uberjar:
|
To build the uberjar:
|
||||||
|
|
||||||
```clojure
|
```bash
|
||||||
clojure -T:build uber
|
clojure -T:build :uber/cli
|
||||||
```
|
```
|
||||||
|
|
||||||
There is a utility script to build a native executable using Graal VM. Please, edit the script and
|
There is a utility script to build a native executable using Graal VM. Please, edit the script and
|
||||||
|
|||||||
201
build.clj
201
build.clj
@@ -1,37 +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-totp)
|
(def lib-group "es.rcorral")
|
||||||
(def version (format "1.2.%s" (b/git-count-revs nil)))
|
(def artifact-prefix "clj-totp")
|
||||||
(def target-dir "target")
|
(def subprojs-base "projects")
|
||||||
(def class-dir (str target-dir "/classes"))
|
(def curr-version (format "2.0.%s" (b/git-count-revs nil)))
|
||||||
(def uber-file (format "target/%s-%s-standalone.jar" (name lib) version))
|
|
||||||
|
|
||||||
;; delay to defer side effects (artifact downloads)
|
|
||||||
(def basis (delay (b/create-basis {:project "deps.edn"})))
|
|
||||||
|
|
||||||
(defn clean [_]
|
|
||||||
(b/delete {:path "target"}))
|
|
||||||
|
|
||||||
(defn compile-java [_]
|
|
||||||
(b/javac {:src-dirs ["java"]
|
|
||||||
:class-dir class-dir
|
|
||||||
:basis @basis
|
|
||||||
:javac-opts ["-source" "11" "--target" "11" "-proc:none"]}))
|
|
||||||
|
|
||||||
|
|
||||||
|
;; Builds artifact's full descriptor for each subproject
|
||||||
|
(defn lib [subproj]
|
||||||
|
(symbol (str lib-group "/" artifact-prefix "-" subproj )))
|
||||||
|
|
||||||
|
|
||||||
|
;; Basis for each subproject, using their own deps.edn
|
||||||
|
;; Injects :extra-deps from :build as additional dependencies
|
||||||
|
(defn basis [subproj]
|
||||||
|
(delay (b/create-basis {:project (str subprojs-base "/" subproj "/deps.edn")
|
||||||
|
;; Inject extra deps as deps
|
||||||
|
:extra {:deps (get-in (basis/initial-basis) [:aliases :build :extra-deps])}
|
||||||
|
})))
|
||||||
|
|
||||||
|
|
||||||
|
;; Show basis generated for a subproject
|
||||||
#_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn uber [_]
|
(defn show-basis [subproj]
|
||||||
(clean nil)
|
(println (with-out-str
|
||||||
(b/copy-dir {:src-dirs ["src"]
|
(pp/pprint
|
||||||
:target-dir class-dir})
|
@(basis subproj)
|
||||||
(b/copy-file {:src "resources/clj-totp.sh"
|
;(basis/initial-basis)
|
||||||
:target "target/clj-totp.sh"})
|
))))
|
||||||
(compile-java nil)
|
|
||||||
(b/compile-clj {:basis @basis
|
(comment
|
||||||
:ns-compile '[totp.app]
|
(pp/pprint (keys (basis/initial-basis)))
|
||||||
:class-dir class-dir})
|
(pp/pprint (:deps (basis/initial-basis)))
|
||||||
(b/uber {:class-dir class-dir
|
(pp/pprint (:libs (basis/initial-basis)))
|
||||||
:uber-file uber-file
|
(pp/pprint (sort (keys (:aliases (basis/initial-basis)))))
|
||||||
:basis @basis
|
(get-in (basis/initial-basis) [:aliases :build :extra-deps])
|
||||||
:main 'totp.app}))
|
)
|
||||||
|
|
||||||
|
;; 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 )
|
||||||
|
|
||||||
|
)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/env sh
|
#!/bin/env sh
|
||||||
|
|
||||||
protoc --java_out java/protoc/ resources/proto/otpauth-migration.proto
|
protoc --java_out projects/core/java/protoc/ projects/core/resources/proto/otpauth-migration.proto
|
||||||
#javac -cp resources/protobuf-java-3.25.8.jar -d target/classes/proto src/OtpauthMigration.java
|
#javac -cp resources/protobuf-java-3.25.8.jar -d target/classes/proto src/OtpauthMigration.java
|
||||||
|
|||||||
70
deps.edn
70
deps.edn
@@ -1,24 +1,68 @@
|
|||||||
{: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"}
|
;; 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
|
||||||
com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"} ;; Tutorial: https://shagunagrawal.me/posts/setup-clojure-with-graalvm-for-native-image/
|
clj-totp/core {:local/root "projects/core"}
|
||||||
;; Protobuf for java
|
clj-totp/cli {:local/root "projects/cli"}
|
||||||
com.google.protobuf/protobuf-java {:mvn/version "3.25.8"}
|
clj-totp/web {:local/root "projects/web"}
|
||||||
;; Progress bar
|
clj-totp/gui {:local/root "projects/gui"}
|
||||||
com.github.pmonks/spinner {:mvn/version "2.0.284"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
: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"]
|
;; 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"}}}}
|
||||||
|
|
||||||
|
|||||||
34
doc/db.plantuml
Normal file
34
doc/db.plantuml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
@startuml
|
||||||
|
' configuration
|
||||||
|
skinparam linetype ortho
|
||||||
|
|
||||||
|
entity "user" as user {
|
||||||
|
id: number
|
||||||
|
--
|
||||||
|
login: varchar(64)
|
||||||
|
passw: varchar(512)
|
||||||
|
active: shortint
|
||||||
|
desc: varchar(512)
|
||||||
|
config: varchar(512)
|
||||||
|
}
|
||||||
|
|
||||||
|
entity "app" as app {
|
||||||
|
id: number
|
||||||
|
--
|
||||||
|
name: varchar(32)
|
||||||
|
desc: varchar(512)
|
||||||
|
secret: varchar(512)
|
||||||
|
period: int
|
||||||
|
config: varchar(512)
|
||||||
|
}
|
||||||
|
|
||||||
|
entity "user_app" as user_app {
|
||||||
|
user_id: number
|
||||||
|
app_id: number
|
||||||
|
--
|
||||||
|
}
|
||||||
|
|
||||||
|
user ||--o{ user_app
|
||||||
|
app ||--o{ user_app
|
||||||
|
|
||||||
|
@enduml
|
||||||
BIN
doc/db.png
Normal file
BIN
doc/db.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
@@ -4,7 +4,7 @@ NATIVE=~/.sdkman/candidates/java/21.0.2-graalce/bin/native-image
|
|||||||
BIN_FILE=totp
|
BIN_FILE=totp
|
||||||
|
|
||||||
echo "Creating uberjar"
|
echo "Creating uberjar"
|
||||||
#clojure -T:build uber
|
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"
|
||||||
@@ -27,4 +27,4 @@ cp target/$BIN_FILE ~/bin
|
|||||||
echo "Copied to ~/bin/$BIN_FILE"
|
echo "Copied to ~/bin/$BIN_FILE"
|
||||||
|
|
||||||
echo "Compress executable for distribution"
|
echo "Compress executable for distribution"
|
||||||
xz target/$BIN_FILE
|
xz -fv target/$BIN_FILE
|
||||||
|
|||||||
15
projects/cli/deps.edn
Executable file
15
projects/cli/deps.edn
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
{: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"}}
|
||||||
|
|
||||||
|
: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
projects/cli/tests.edn
Normal file
1
projects/cli/tests.edn
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#kaocha/v1 {}
|
||||||
1
projects/core/.cpcache/4091673994.basis
Normal file
1
projects/core/.cpcache/4091673994.basis
Normal file
File diff suppressed because one or more lines are too long
1
projects/core/.cpcache/4091673994.cp
Normal file
1
projects/core/.cpcache/4091673994.cp
Normal file
File diff suppressed because one or more lines are too long
1
projects/core/.cpcache/425892293.basis
Normal file
1
projects/core/.cpcache/425892293.basis
Normal file
File diff suppressed because one or more lines are too long
1
projects/core/.cpcache/425892293.cp
Normal file
1
projects/core/.cpcache/425892293.cp
Normal file
File diff suppressed because one or more lines are too long
2
projects/core/.cpcache/425892293.main
Normal file
2
projects/core/.cpcache/425892293.main
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-m
|
||||||
|
kaocha.runner
|
||||||
12
projects/core/deps.edn
Executable file
12
projects/core/deps.edn
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
{: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"}
|
||||||
|
}
|
||||||
|
|
||||||
|
: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"]}}}
|
||||||
|
|
||||||
@@ -99,3 +99,29 @@
|
|||||||
(rem (int (m/pow 10 digits))))))))
|
(rem (int (m/pow 10 digits))))))))
|
||||||
([secret]
|
([secret]
|
||||||
(get-otp secret "sha1" 6 30)))
|
(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")
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -89,11 +89,16 @@
|
|||||||
|
|
||||||
(defn list-apps
|
(defn list-apps
|
||||||
[cfg]
|
[cfg]
|
||||||
(map :name
|
(->> cfg
|
||||||
(filter #(contains? % :name) cfg)))
|
(filter #(contains? % :name))
|
||||||
|
(map :name)))
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
(list-apps (load-config)))
|
(list-apps (load-config))
|
||||||
|
(with-config #_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(list-apps cfg))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
(defn get-app
|
(defn get-app
|
||||||
[cfg name]
|
[cfg name]
|
||||||
186
projects/core/src/totp/otp_import.clj
Normal file
186
projects/core/src/totp/otp_import.clj
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
(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/
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(let [encoded "CkkKEJ0M4MyHfITKCwCfqPIttjESFHJ1YmVuY2pAMThCMTY5RDVGRjAwGgRTTldMIAEoATACQhMzYjkxMDQxNzI3NzgzNDIzNDYyEAIYASAA"
|
||||||
|
decoded (b64/decode encoded)]
|
||||||
|
(print (hex/encode decoded))
|
||||||
|
(print (b/to-string decoded))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(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 ) )
|
||||||
|
"
|
||||||
|
[x n]
|
||||||
|
(case x
|
||||||
|
nil 0 ;; nill is encoded with zero bytes
|
||||||
|
0 1 ;; One block to zero
|
||||||
|
9223372036854775807 (len-bits (dec x) n) ;; Beware the overflow!! it's best to lose some precision
|
||||||
|
(when (and (>= x 0) (some? n) (> n 0))
|
||||||
|
(int (m/ceil (/ (m/log (inc x)) (m/log (m/pow 2 n))))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(len-bits 513 8)
|
||||||
|
(len-bits Long/MAX_VALUE 8)
|
||||||
|
(len-bits (dec Long/MAX_VALUE) 8)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defn len-bytes
|
||||||
|
"How may bytes are needed to encode this number?"
|
||||||
|
[x]
|
||||||
|
(len-bits x 8))
|
||||||
|
|
||||||
|
|
||||||
|
(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 decimal-to-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.
|
||||||
|
"
|
||||||
|
([n base]
|
||||||
|
(decimal-to-base n base true))
|
||||||
|
([n base reverse?]
|
||||||
|
(when (and n base)
|
||||||
|
(if (or (nil? n) (nil? base) (zero? n) (zero? base)) ;; Allways [0] for base zero or number zero
|
||||||
|
[0]
|
||||||
|
(loop [acc []
|
||||||
|
x n]
|
||||||
|
(if (zero? x)
|
||||||
|
(vec (if reverse? (reverse acc) acc)) ;; When x is zero, we have finished
|
||||||
|
(recur
|
||||||
|
(conj acc (rem x base)) ;; Accumulate the remainder
|
||||||
|
(quot x base)))))))) ;; Pass the quotient to the next step
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(decimal-to-base 8 2)
|
||||||
|
2r1000
|
||||||
|
)
|
||||||
|
|
||||||
|
(defn base-to-decimal
|
||||||
|
"Converts from an array with values in an arbitrary base into decimal values"
|
||||||
|
[n base]
|
||||||
|
(if (or (nil? n) (nil? base) (zero? (count n)))
|
||||||
|
0
|
||||||
|
(int (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])
|
||||||
|
|
||||||
|
(reduce-kv
|
||||||
|
(fn [acc k v]
|
||||||
|
(+ acc (* v (m/pow 2 k))))
|
||||||
|
0 (vec (reverse [1 0 0 0])))
|
||||||
|
|
||||||
|
(base-to-decimal [1 0 0 0] 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
(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-to-base x 128 false)]
|
||||||
|
(byte-array (conj
|
||||||
|
(vec (map #(bit-or 2r10000000 %) (butlast b128)))
|
||||||
|
(bit-and 2r01111111 (peek b128))))))
|
||||||
|
|
||||||
|
|
||||||
|
(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))))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(hex/decode (String/format "%08x" (into-array [1023])))
|
||||||
|
|
||||||
|
(to-fancy-bin [150] )
|
||||||
|
|
||||||
|
(to-fancy-bin (int->varint 150))
|
||||||
|
(to-fancy-bin (int->varint 151))
|
||||||
|
|
||||||
|
(to-fancy-hex (int->varint 150))
|
||||||
|
(to-fancy-hex (int->varint 151) 4)
|
||||||
|
|
||||||
|
|
||||||
|
(to-fancy-bin (int->varint Long/MAX_VALUE))
|
||||||
|
(to-fancy-bin (int->varint (dec Long/MAX_VALUE)))
|
||||||
|
|
||||||
|
(to-fancy-hex (int->varint (dec Long/MAX_VALUE)))
|
||||||
|
|
||||||
|
(to-fancy-bin [2r10010110 2r00000001])
|
||||||
|
|
||||||
|
[2r10010110 2r00000001]
|
||||||
|
[2r0010110 2r0000001]
|
||||||
|
)
|
||||||
BIN
projects/core/target/clj-totp-core-1.2.69-standalone.jar
Normal file
BIN
projects/core/target/clj-totp-core-1.2.69-standalone.jar
Normal file
Binary file not shown.
BIN
projects/core/target/clj-totp-core-1.2.69.jar
Normal file
BIN
projects/core/target/clj-totp-core-1.2.69.jar
Normal file
Binary file not shown.
8
projects/core/target/clj-totp.sh
Executable file
8
projects/core/target/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 $@
|
||||||
83
projects/core/test/totp/otp_import_test.clj
Normal file
83
projects/core/test/totp/otp_import_test.clj
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
(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]))
|
||||||
|
|
||||||
|
|
||||||
|
(deftest len-bits-test
|
||||||
|
(testing "Check required number of blocks to encode a number in n bits"
|
||||||
|
(is (nil? (len-bits 10 nil)))
|
||||||
|
(is (= 0 (len-bits nil 10)))
|
||||||
|
(is (= 1 (len-bits 1 2)))
|
||||||
|
(is (= 2 (len-bits 10 2)))
|
||||||
|
(is (= 2 (len-bits 15 2)))
|
||||||
|
(is (= 3 (len-bits 16 2)))
|
||||||
|
(is (= 1 (len-bits 255 8)))
|
||||||
|
(is (= 2 (len-bits 255 7)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(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-to-base-test
|
||||||
|
(testing "Convert from decimal base to an arbitrary base"
|
||||||
|
(is (= [0] (decimal-to-base 10 nil)))
|
||||||
|
(is (= [0] (decimal-to-base nil 10)))
|
||||||
|
(is (= [0] (decimal-to-base 0 2)))
|
||||||
|
(is (= [0] (decimal-to-base 2 0)))
|
||||||
|
(is (= [1 0 0 0] (decimal-to-base 8 2)))
|
||||||
|
(is (= [2 2] (decimal-to-base 8 3)))
|
||||||
|
(is (= [2 0] (decimal-to-base 8 4)))
|
||||||
|
(is (= [1 3] (decimal-to-base 8 5)))
|
||||||
|
(is (= [3 1] (decimal-to-base 8 5 false)))
|
||||||
|
(is (= [0] (decimal-to-base 0 5)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(deftest base-to-decimal-test
|
||||||
|
(testing "Convert from arbitrary base to decimal"
|
||||||
|
(is (zero? (base-to-decimal nil 2)))
|
||||||
|
(is (zero? (base-to-decimal [] 2)))
|
||||||
|
(is (zero? (base-to-decimal [1] nil)))
|
||||||
|
(is (= 8 (base-to-decimal [1 0 0 0] 2)))
|
||||||
|
(is (= 8 (base-to-decimal [2 2] 3)))
|
||||||
|
(is (= 8 (base-to-decimal [2 0] 4)))
|
||||||
|
(is (= 8 (base-to-decimal [1 3] 5)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(deftest decimal-base-decimal-test
|
||||||
|
(testing "Check if convert from decimal to a base and back preserves the original number"
|
||||||
|
(is (= 8 (base-to-decimal (decimal-to-base 8 2) 2)))
|
||||||
|
(is (= 127 (base-to-decimal (decimal-to-base 127 2) 2)))
|
||||||
|
(is (= 417 (base-to-decimal (decimal-to-base 417 13) 13)))
|
||||||
|
))
|
||||||
1
projects/core/tests.edn
Normal file
1
projects/core/tests.edn
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#kaocha/v1 {}
|
||||||
7
projects/gui/deps.edn
Executable file
7
projects/gui/deps.edn
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
{: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"}}
|
||||||
|
}
|
||||||
|
|
||||||
166
projects/gui/src/totp/gui.clj
Normal file
166
projects/gui/src/totp/gui.clj
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
(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]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn content-test
|
||||||
|
[]
|
||||||
|
(let [choose (fn [e] (alert "I should open a file chooser"))]
|
||||||
|
(flow-panel
|
||||||
|
:items ["File" [:fill-h 5]
|
||||||
|
(text (System/getProperty "user.dir")) [:fill-h 5]
|
||||||
|
(action :handler choose :name "...")])))
|
||||||
|
|
||||||
|
|
||||||
|
(defn content-test2
|
||||||
|
[name category date comment]
|
||||||
|
(mig-panel
|
||||||
|
:constraints ["wrap 2"
|
||||||
|
"[shrink 0]20px[200, grow, fill]"
|
||||||
|
"[shrink 0]5px[]"]
|
||||||
|
:items [["name:"] [(text (or name ""))]
|
||||||
|
["category:"] [(text (or category ""))]
|
||||||
|
["date:"] [(text (or date ""))]
|
||||||
|
["comment:"] [(text (or comment ""))]]))
|
||||||
|
|
||||||
|
|
||||||
|
(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"
|
||||||
|
[]
|
||||||
|
(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)
|
||||||
|
:east (text :id "timer-text"
|
||||||
|
:text init-val
|
||||||
|
:editable? false
|
||||||
|
:columns 2
|
||||||
|
:halign :right)
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
(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 "SOUTH"
|
||||||
|
;: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!
|
||||||
|
start-updater))
|
||||||
|
(println "Gui started"))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; This kills your REPL connection
|
||||||
|
(-main)
|
||||||
|
|
||||||
|
(show-options (frame))
|
||||||
|
(show-options (text))
|
||||||
|
|
||||||
|
)
|
||||||
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"]}}}
|
||||||
|
|
||||||
68
projects/web/src/totp/db/datomic.clj
Normal file
68
projects/web/src/totp/db/datomic.clj
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
(ns totp.db.datomic
|
||||||
|
(: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 cfg {:server-type :datomic-local
|
||||||
|
:system "local-data"
|
||||||
|
:storage-dir db-path})
|
||||||
|
|
||||||
|
(def client (d/client cfg))
|
||||||
|
|
||||||
|
;; Schema for our database
|
||||||
|
|
||||||
|
(def totp-schema [{:db/ident :app/name
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/unique :db.unique/identity
|
||||||
|
:db/doc "Unique name of the application. Between 2 and 32 chars"
|
||||||
|
:db.attr/preds (fn [s] (<= 3 (count s) 15))}
|
||||||
|
{:db/ident :app/desc
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/doc "(optional) Description of the application"}
|
||||||
|
{:db/ident :app/secret
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/doc "Secret BASE32"}
|
||||||
|
{:db/ident :app/period
|
||||||
|
:db/valueType :db.type/long
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/doc "Time slice in seconds (30 by default)"}
|
||||||
|
{:db/ident :app/config
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/doc "(Optional) Extra config"}
|
||||||
|
|
||||||
|
{:db/ident :user/login
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/unique :db.unique/identity
|
||||||
|
:db/doc "Identifier for the user"}
|
||||||
|
{:db/ident :user/passwd
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/doc "Password"}
|
||||||
|
{:db/ident :user/active
|
||||||
|
:db/valueType :db.type/boolean
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/doc "Is the user active?"}
|
||||||
|
{:db/ident :user/desc
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/doc "(Optional) Description of the user"}
|
||||||
|
{:db/ident :user/config
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/doc "(Optional) Extra config"}
|
||||||
|
{:db/ident :user/apps
|
||||||
|
:db/valueType :db.type/ref
|
||||||
|
:db/cardinality :db.cardinality/many
|
||||||
|
:db/doc "Applications for this user"}])
|
||||||
|
|
||||||
|
(defn init-db
|
||||||
|
[client]
|
||||||
|
(d/create-database client {:db-name "totp"}))
|
||||||
23
projects/web/src/totp/db/sqlite.clj
Normal file
23
projects/web/src/totp/db/sqlite.clj
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
(ns totp.db.sqlite
|
||||||
|
(:require [next.jdbc :as jdbc]))
|
||||||
|
|
||||||
|
;; DB configuration
|
||||||
|
(def db {:dbname "totp-data.sqlite"
|
||||||
|
:dbtype "sqlite"})
|
||||||
|
|
||||||
|
;; DB parsed config
|
||||||
|
(def data-source (jdbc/get-datasource db))
|
||||||
|
|
||||||
|
(defn init-db
|
||||||
|
"Create an empty DB"
|
||||||
|
[]
|
||||||
|
(jdbc/execute! data-source ["
|
||||||
|
create table apps (
|
||||||
|
id int auto_increment primary key,
|
||||||
|
name varchar(32),
|
||||||
|
desc varchar(255)
|
||||||
|
)"]))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(init-db)
|
||||||
|
)
|
||||||
1
projects/web/tests.edn
Normal file
1
projects/web/tests.edn
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#kaocha/v1 {}
|
||||||
BIN
projects/web/totp-data.sqlite
Normal file
BIN
projects/web/totp-data.sqlite
Normal file
Binary file not shown.
@@ -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"
|
||||||
|
}}
|
||||||
Reference in New Issue
Block a user