Compare commits

21 Commits

Author SHA1 Message Date
5651cc1ab2 Merge pull request 'release/1.0' (#1) from release/1.0 into main
Reviewed-on: #1
2025-09-01 18:38:12 +02:00
96ed6ae1e9 Updated READMEç 2025-09-01 18:33:47 +02:00
d8c3f5ee67 Deleting unfinished features 2025-09-01 10:37:11 +02:00
9eeb3571d5 bye bye, intellij config 2025-08-28 21:06:41 +02:00
58a17dc5bd Update README.md 2025-08-28 13:59:54 +02:00
4f29260f9b Native compilation 2025-08-28 13:47:51 +02:00
df82cf3f44 Small typo 2025-08-28 12:27:14 +02:00
f32986f2db Update README.md 2025-08-28 10:17:41 +02:00
1205a79f19 Continuous mode is working 2025-08-28 10:13:10 +02:00
38126f987a continuous generation 2025-08-27 23:57:34 +02:00
fa0de1f624 using a file as config backend (for now)ç 2025-08-27 23:53:18 +02:00
efec0ca07c diagram 2025-08-26 16:14:22 +02:00
4361782861 database diagram 2025-08-26 16:01:49 +02:00
8d0fb81a9d Testing datomic 2025-08-26 15:00:34 +02:00
4a1abd7fd7 Build uberjar using build.clj 2025-08-26 12:19:28 +02:00
b7d3c6ce86 Ignore 'target' directory 2025-08-26 11:31:00 +02:00
0a4e531b2f Adding an alias to run easily 2025-08-25 09:32:52 +02:00
a22f4a5670 Implementing SQLite database for config 2025-08-24 17:52:30 +02:00
f408834726 Using subcommands for CLI 2025-08-24 12:26:19 +02:00
09d8cd0e10 Check for byte array wasn't consistent enough 2025-08-24 11:41:16 +02:00
d448ebe001 Simple CLI usage 2025-08-24 02:27:02 +02:00
48 changed files with 369 additions and 393 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
/.clj-kondo/
/.cpcache/
/.lsp/
/target/
.nrepl-port
.calva

3
.idea/.gitignore generated vendored
View File

@@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ClojureProjectResolveSettings">
<currentScheme>IDE</currentScheme>
</component>
</project>

6
.idea/clj-kondo.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KondoProjectSettings">
<option name="useCljKondo" value="YES" />
</component>
</project>

18
.idea/clojure-deps.xml generated
View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DepsProjectsManager">
<option name="dirTypeMappings">
<set>
<FolderState>
<option name="dirUrl" value="file://$PROJECT_DIR$/test" />
<option name="type" value="TEST_SOURCE" />
</FolderState>
</set>
</option>
<option name="projectFiles">
<set>
<option value="file://$PROJECT_DIR$/deps.edn" />
</set>
</option>
</component>
</project>

View File

@@ -1,8 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>hotp</w>
<w>topt</w>
</words>
</dictionary>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: aero:1.1.6">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/aero/aero/1.1.6/aero-1.1.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: clojure.java-time:1.4.3">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/clojure/java-time/clojure.java-time/1.4.3/clojure.java-time-1.4.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: com.nextjournal/beholder:1.0.2">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/nextjournal/beholder/1.0.2/beholder-1.0.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: expound:0.9.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/expound/expound/0.9.0/expound-0.9.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: fipp:0.6.26">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/fipp/fipp/0.6.26/fipp-0.6.26.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: hawk:0.2.11">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/hawk/hawk/0.2.11/hawk-0.2.11.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: io.methvin/directory-watcher:0.17.3">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/methvin/directory-watcher/0.17.3/directory-watcher-0.17.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: lambdaisland/clj-diff:1.4.78">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/lambdaisland/clj-diff/1.4.78/clj-diff-1.4.78.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: lambdaisland/deep-diff2:2.11.216">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/lambdaisland/deep-diff2/2.11.216/deep-diff2-2.11.216.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: lambdaisland/kaocha:1.91.1392">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/lambdaisland/kaocha/1.91.1392/kaocha-1.91.1392.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: lambdaisland/tools.namespace:0.3.256">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/lambdaisland/tools.namespace/0.3.256/tools.namespace-0.3.256.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: meta-merge:1.0.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/meta-merge/meta-merge/1.0.0/meta-merge-1.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: mvxcvi/arrangement:2.1.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/mvxcvi/arrangement/2.1.0/arrangement-2.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: net.incongru.watchservice/barbary-watchservice:1.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/net/incongru/watchservice/barbary-watchservice/1.0/barbary-watchservice-1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: net.java.dev.jna/jna:5.12.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.12.1/jna-5.12.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: org.clojure/clojure:1.12.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/clojure/clojure/1.12.1/clojure-1.12.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: org.clojure/core.rrb-vector:0.1.2">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/clojure/core.rrb-vector/0.1.2/core.rrb-vector-0.1.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: org.clojure/core.specs.alpha:0.4.74">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/clojure/core.specs.alpha/0.4.74/core.specs.alpha-0.4.74.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: org.clojure/java.classpath:1.0.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/clojure/java.classpath/1.0.0/java.classpath-1.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: org.clojure/spec.alpha:0.5.238">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/clojure/spec.alpha/0.5.238/spec.alpha-0.5.238.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: org.clojure/tools.cli:1.1.230">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/clojure/tools.cli/1.1.230/tools.cli-1.1.230.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: org.clojure/tools.reader:1.3.6">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/clojure/tools.reader/1.3.6/tools.reader-1.3.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: org.slf4j/slf4j-api:1.7.36">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: org.tcrawley/dynapath:1.1.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/tcrawley/dynapath/1.1.0/dynapath-1.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: progrock:0.1.2">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/progrock/progrock/0.1.2/progrock-0.1.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="Deps: slingshot:0.12.2">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/slingshot/slingshot/0.12.2/slingshot-0.12.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

6
.idea/misc.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="IDE SDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/clj-totp.iml" filepath="$PROJECT_DIR$/clj-totp.iml" />
</modules>
</component>
</project>

View File

@@ -1,12 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="REPL for clj-totp" type="ClojureREPL" factoryName="Local" activateToolWindowBeforeRun="false">
<option name="configVersion" value="1" />
<option name="displayName" value="REPL for clj-totp" />
<option name="execution" value="DEPS" />
<module name="clj-totp" />
<option name="options" />
<option name="profiles" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<method v="2" />
</configuration>
</component>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -1,33 +1,55 @@
# clj-totp
TOTP (Time-based One Time Password) in clojure. It can be used in the command line, web API o simple embedded web.
## What is TOPT
The TOPT is a standard used to generate a time-based password. Usually, this password is used as a second
factor authentication.
You can read more about the algorithm here:
- Wikipedia: https://en.wikipedia.org/wiki/Time-based_one-time_password
- TOTP RFC: https://web.archive.org/web/20110711124823/http://tools.ietf.org/html/rfc6238
- HOTP RFC: https://www.ietf.org/rfc/rfc4226.txt
## The inside
This project is done 100% in clojure. It uses `deps.edn` for configuring the project.
## Implementation timeline
### v1.0
- [ ] Functional TOTP generation
- [ ] Get TOTP from command line
- [ ] Store configuration in a simple BD (sqlite, for example)
### v1.1
- [ ] REST API
- [ ] User management
### v1.2
- [ ] Simple web connected to REST API
# clj-totp
TOTP (Timebased One Time Password) in clojure. It can be used in the command line, web API o simple embeded web.
## What is TOPT
The TOPT is an standad used to generate a time-based password. Usually, this password is used as a second
factor authentication.
You can red more about the algorith here:
- Wikipedia: https://en.wikipedia.org/wiki/Time-based_one-time_password
- TOTP RFC: https://web.archive.org/web/20110711124823/http://tools.ietf.org/html/rfc6238
- HOTP RFC: https://www.ietf.org/rfc/rfc4226.txt
## The inside
This project is done 100% in clojure. It uses `deps.edn` for configuring the project.
## Features
### v1.0
- Functional TOTP generation
- Get TOTP from command line
- Continuous update every 30 seconds
## Usage
You can use the `clojure` command to run the program:
```
clojure -M:run <params>
```
If you prefer using the distributed jar:
```
java -jar clj-topt-1.0.35-standalone.jar <params>
```
You can use the binary (compiled with GraalVM) in linux environments:
```
totp <params>
```
All three methods are equivalent.
### Generate a single TOTP
You can simple run:
```
totp generate <secret in BASE32>
```
If want to update coninously the generated TOTP, you cand add the `-s` param:
```
totp generate <secret in BASE32> -s
```

25
build.clj Normal file
View File

@@ -0,0 +1,25 @@
(ns build
(:require [clojure.tools.build.api :as b]))
(def lib 'es.rcorral/clj-topt)
(def version (format "1.0.%s" (b/git-count-revs nil)))
(def class-dir "target/classes")
(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 uber [_]
(clean nil)
(b/copy-dir {:src-dirs ["src" "resources"]
:target-dir class-dir})
(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>

1
collect-deps.sh Executable file
View File

@@ -0,0 +1 @@
~/.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

View File

@@ -1,11 +1,19 @@
{:paths ["src"]
:deps {clojure.java-time/clojure.java-time {:mvn/version "1.4.3"}
mvxcvi/alphabase {:mvn/version "3.0.185"}}
: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
;; 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 {:test {:extra-paths ["test"] ;; https://cljdoc.org/d/uberdeps/uberdeps/1.4.0/doc/readme
: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"] ;; https://cljdoc.org/d/uberdeps/uberdeps/1.4.0/doc/readme
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
:main-opts ["-m" "kaocha.runner"]}
:uberdeps {
:replace-deps {uberdeps/uberdeps {:mvn/version "1.4.0"}}
:replace-paths []
:main-opts ["-m" "uberdeps.uberjar"]}}}
;; Run with clj -T:build function-in-build
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
:ns-default build}}}

34
doc/db.plantuml Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

14
native.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/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"

52
reflect_config.json Normal file
View File

@@ -0,0 +1,52 @@
[
{
"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"
]
}
]
}
]

View File

@@ -1,3 +1,64 @@
(ns totp.app
( :require [totp.core :refer :all]))
(:require [totp.core :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"
:description ["Generate a TOTP"]}
:commands [{:command "generate" :short "g"
:description "Generate one TOTP with a given secret in BASE32"
:examples ["Generate one TOTP and exit:"
" totp generate \"MJXW42LBORXQ====\""
"Generate one TOTP, update each 30 seconds:"
" totp g -c \"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))

View File

@@ -5,19 +5,20 @@
(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
(quot (/ time 1000) step-size)))
(int (quot time (* 1000 step-size)))))
(defn bytes-array?
"Return true if x is a byte[]"
[x]
(= byte/1 (type x)))
(= byte-array-type (type x)))
(defmulti hmac-sha1
@@ -25,7 +26,7 @@
(fn [key message]
(cond
(and (string? key) (string? message)) :string
(and (= byte/1 (type key)) (= byte/1 (type message))) :byte
(and (bytes-array? key) (bytes-array? message)) :byte
:else :nil)))
;; By default

106
src/totp/data.clj Normal file
View File

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