Compare commits
33 Commits
75075b81fb
...
v1.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b33e18be5 | |||
| 173793caa3 | |||
| 01842dbc8d | |||
| cec35fc16b | |||
| e5fb6e7231 | |||
| 6c017b3262 | |||
| 2e64c26a0a | |||
| c8b9556bcd | |||
| 5c93f4e570 | |||
| dfc3d4e579 | |||
| e7b2683d2c | |||
| 8698e6b57b | |||
| 3dd79af7de | |||
| 386d4f7434 | |||
| 82b1407489 | |||
| d86054f3a3 | |||
| 547e143f0c | |||
| 48478c49bc | |||
| ba58c7d744 | |||
| 32cf9cb581 | |||
| 29a1061d18 | |||
| 13be73f7e2 | |||
| 38586187e9 | |||
| af987008b0 | |||
| ba393ec55a | |||
| a85fc61e16 | |||
| 7b629b4b0d | |||
| 72923a34ff | |||
| fd012eea00 | |||
| 2aeef9925d | |||
| 5651cc1ab2 | |||
| 96ed6ae1e9 | |||
| d8c3f5ee67 |
167
README.md
167
README.md
@@ -1,6 +1,7 @@
|
||||
# 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
|
||||
|
||||
@@ -14,27 +15,179 @@ You can read more about the algorithm here:
|
||||
- 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.
|
||||
First, you must have installed a Java Runtime Environment. Check https://adoptium.net/es if you are
|
||||
unsure how to install.
|
||||
|
||||
## Implementation timeline
|
||||
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
|
||||
- [x] Functional TOTP generation
|
||||
- [x] Get TOTP from command line
|
||||
- [x] Continuous generation
|
||||
- [ ] Store configuration in a properties file or simple DB
|
||||
|
||||
### 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
|
||||
- [ ] REST API
|
||||
- [ ] User management
|
||||
- [ ] Robust BD backend (H2, datomic, or similar)
|
||||
|
||||
### v1.2
|
||||
### v3
|
||||
- [ ] Simple web connected to REST API
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
## 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:
|
||||
|
||||
```clojure
|
||||
clojure -M:run <commands and parameters>
|
||||
```
|
||||
|
||||
To build the uberjar:
|
||||
|
||||
```clojure
|
||||
clojure -T:build uber
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
20
build.clj
20
build.clj
@@ -1,9 +1,10 @@
|
||||
(ns build
|
||||
(:require [clojure.tools.build.api :as b]))
|
||||
|
||||
(def lib 'es.rcorral/clj-topt)
|
||||
(def version (format "1.1.%s" (b/git-count-revs nil)))
|
||||
(def class-dir "target/classes")
|
||||
(def lib 'es.rcorral/clj-totp)
|
||||
(def version (format "1.2.%s" (b/git-count-revs nil)))
|
||||
(def target-dir "target")
|
||||
(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)
|
||||
@@ -12,10 +13,21 @@
|
||||
(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"]}))
|
||||
|
||||
|
||||
#_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn uber [_]
|
||||
(clean nil)
|
||||
(b/copy-dir {:src-dirs ["src" "resources"]
|
||||
(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})
|
||||
|
||||
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>
|
||||
4
compile_proto.sh
Executable file
4
compile_proto.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/env sh
|
||||
|
||||
protoc --java_out java/protoc/ resources/proto/otpauth-migration.proto
|
||||
#javac -cp resources/protobuf-java-3.25.8.jar -d target/classes/proto src/OtpauthMigration.java
|
||||
18
deps.edn
18
deps.edn
@@ -1,24 +1,24 @@
|
||||
{:paths ["src"]
|
||||
{: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
|
||||
;; 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)
|
||||
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"} ;; Tutorial: https://shagunagrawal.me/posts/setup-clojure-with-graalvm-for-native-image/
|
||||
;; Protobuf for java
|
||||
com.google.protobuf/protobuf-java {:mvn/version "3.25.8"}
|
||||
;; 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"] ;; https://cljdoc.org/d/uberdeps/uberdeps/1.4.0/doc/readme
|
||||
:test {:extra-paths ["test"]
|
||||
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
|
||||
:main-opts ["-m" "kaocha.runner"]}
|
||||
|
||||
;; Run with clj -T:build function-in-build
|
||||
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
||||
:ns-default build}}}
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
@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
BIN
doc/db.png
Binary file not shown.
|
Before Width: | Height: | Size: 7.9 KiB |
2913
java/protoc/OtpauthMigration.java
Normal file
2913
java/protoc/OtpauthMigration.java
Normal file
File diff suppressed because it is too large
Load Diff
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
|
||||
|
||||
|
||||
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 +1,22 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
NATIVE=~/.sdkman/candidates/java/21.0.2-graalce/bin/native-image
|
||||
UBERJAR=clj-topt-1.0.38-standalone.jar
|
||||
BIN_FILE=totp
|
||||
|
||||
echo "Creating uberjar"
|
||||
clojure -T:build uber
|
||||
UBERJAR=$(realpath --relative-to=target target/clj-totp-*-standalone.jar)
|
||||
|
||||
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\
|
||||
--initialize-at-build-time=org.fusesource.jansi.Ansi\
|
||||
#--trace-class-initialization=org.fusesource.jansi.Ansi
|
||||
|
||||
echo "Executable created on target/$BIN_FILE"
|
||||
cp target/$BIN_FILE ~/bin
|
||||
echo "Copied to ~/bin/$BIN_FILE"
|
||||
|
||||
8
resources/clj-totp.sh
Executable file
8
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
resources/proto/otpauth-migration.proto
Normal file
39
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;
|
||||
}
|
||||
247
src/totp/app.clj
247
src/totp/app.clj
@@ -1,68 +1,134 @@
|
||||
(ns totp.app
|
||||
#_{:clj-kondo/ignore [:refer-all]}
|
||||
(:require [totp.core :refer :all]
|
||||
[totp.data :refer :all]
|
||||
[cli-matic.core :refer [run-cmd]]
|
||||
[cli-matic.utils :as U]
|
||||
[clojure.pprint :as pp]
|
||||
[clojure.pprint :as pprint])
|
||||
[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))
|
||||
([secret algorithm digits period]
|
||||
([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)))
|
||||
fn-show (fn [s] (println (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] []
|
||||
(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 "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)))
|
||||
(read-line))) ;; Waits for a key press
|
||||
;; Waits for a key press
|
||||
(read-line)))
|
||||
|
||||
(comment
|
||||
(print get-otp "MJXW42LBORXQ====")
|
||||
(print-confinuous "MJXW42LBORXQ====")
|
||||
)
|
||||
|
||||
(defn cmd-generate
|
||||
[& {:keys [secret continuous algorithm digits period] :as opts}]
|
||||
[& {:keys [secret continuous algorithm digits period bar bar-style]}]
|
||||
;;(pp/pprint opts)
|
||||
(if continuous
|
||||
(print-confinuous secret algorithm digits period)
|
||||
(print-confinuous secret algorithm digits period bar bar-style)
|
||||
(println (get-otp secret algorithm digits period))))
|
||||
|
||||
|
||||
(defn cmd-get
|
||||
[& {:keys [name continuous] :as opts}]
|
||||
;;(pp/pprint opts)
|
||||
(if (exists-config)
|
||||
(let [cfg (load-config)
|
||||
app (get-app cfg name)
|
||||
{:keys [secret algorithm digits period]
|
||||
:or {algorithm "sha1"
|
||||
digits 6
|
||||
period 30}} app]
|
||||
(if (some? secret)
|
||||
(if continuous
|
||||
(print-confinuous secret algorithm digits period)
|
||||
(println (get-otp secret algorithm digits period)))
|
||||
(println "The app" name "is not configured")))
|
||||
(println "Config file not found.")))
|
||||
|
||||
(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] :as opts}]
|
||||
[& {:keys [command]}]
|
||||
;;(pp/pprint opts)
|
||||
(case command
|
||||
"info" (println "Configuration file: "
|
||||
"info" (println "Configuration file:"
|
||||
(if (exists-config)
|
||||
cfg-file
|
||||
(str "<not found. default:" 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]")
|
||||
@@ -74,32 +140,56 @@
|
||||
"show" (do
|
||||
(println "Config file:\n")
|
||||
;(println (slurp cfg-file))
|
||||
(pp/print-table (load-config))
|
||||
)))
|
||||
(pp/print-table (load-config)))))
|
||||
|
||||
|
||||
(defn cmd-list
|
||||
[& {:keys [sorted] :as opts}]
|
||||
;;(pp/pprint opts)
|
||||
[& {:keys [sorted mode]}]
|
||||
(println "List mode:" mode)
|
||||
(if (exists-config)
|
||||
(let [apps-list (list-apps (load-config))
|
||||
s-list (if sorted (sort apps-list) apps-list)]
|
||||
(dorun (map #(println %) s-list)))
|
||||
(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] :as opts}]
|
||||
[& {:keys [name secret user issuer algorithm digits period update] }]
|
||||
;;(pp/pprint opts)
|
||||
(if (exists-config)
|
||||
(let [cfg (load-config)]
|
||||
(when (or update (nil? (get-app cfg name))) ;; get-app returns nil if app don't exists
|
||||
(store-config (add-app cfg name secret user issuer algorithm digits period))))
|
||||
(println "Config file not found.")))
|
||||
(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] :as opts}]
|
||||
[& {:keys [name force]}]
|
||||
;;(pp/pprint opts)
|
||||
(if (exists-config)
|
||||
(let [cfg (load-config)]
|
||||
@@ -110,10 +200,11 @@
|
||||
|
||||
(def cli-options
|
||||
{:app {:command "totp"
|
||||
:version "1.1"
|
||||
:version "1.2"
|
||||
:description ["Generate a TOTP"]}
|
||||
|
||||
:commands [{:command "generate"
|
||||
: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"
|
||||
@@ -138,9 +229,17 @@
|
||||
{:option "period" :short "p"
|
||||
:as "Validity time in seconds"
|
||||
: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}
|
||||
|
||||
;; 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:"
|
||||
@@ -154,9 +253,17 @@
|
||||
{:option "continuous" :short "c"
|
||||
:as "Contiuous mode"
|
||||
:type :with-flag
|
||||
:default false}]
|
||||
:runs cmd-get}
|
||||
|
||||
: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:"
|
||||
@@ -170,7 +277,7 @@
|
||||
:type #{"info" "init" "show"}
|
||||
:default :present}]
|
||||
:runs cmd-config}
|
||||
|
||||
;; List available apps
|
||||
{:command "list" :short "l"
|
||||
:description "List existing apps"
|
||||
:examples ["List apps:"
|
||||
@@ -178,11 +285,15 @@
|
||||
"List apps sorted by name:"
|
||||
" totp list --sorted"]
|
||||
:opts [{:option "sorted" :short "s"
|
||||
:as "If provided, the list willl be sorted by name"
|
||||
:as "If provided, the list will be sorted by name"
|
||||
:type :with-flag
|
||||
:default false}]
|
||||
: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:"
|
||||
@@ -220,7 +331,27 @@
|
||||
: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"
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
(:require [clojure.edn :as e]
|
||||
[clojure.string :as str]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.pprint :as pp]))
|
||||
[clojure.pprint :as pp]
|
||||
[alphabase.base64 :as b64]
|
||||
[alphabase.base32 :as b32])
|
||||
(:import [protoc OtpauthMigration$MigrationPayload]))
|
||||
|
||||
(defn join-path
|
||||
"Joins several subpaths using system's path separator (/ un *NIX and \\ in windows)"
|
||||
@@ -70,10 +73,18 @@
|
||||
(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) {:name name :secret secret :user user :issuer issuer :algorithm algorithm :digits digits :period period})))
|
||||
(conj (delete-app cfg name) (create-app name secret user issuer algorithm digits period))))
|
||||
|
||||
|
||||
(defn list-apps
|
||||
@@ -86,7 +97,10 @@
|
||||
|
||||
(defn get-app
|
||||
[cfg name]
|
||||
(first (filter #(= name (:name %)) cfg)))
|
||||
(let [app (first (filter #(= name (:name %)) cfg))]
|
||||
(if app
|
||||
app
|
||||
(println "App" name "not found"))))
|
||||
|
||||
|
||||
(comment
|
||||
@@ -106,4 +120,127 @@
|
||||
(add-app "app1" "abc123abc123")
|
||||
(add-app "app2" "abc123abc123")
|
||||
(add-app "app1" "123456789012")
|
||||
(store-config)))
|
||||
(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)
|
||||
parsed (OtpauthMigration$MigrationPayload/parseFrom data-b)
|
||||
payload (bean (.getOtpParameters parsed 0))
|
||||
;{:keys [name secret name issuer digitsValue algorithmValue typeValue]} payload
|
||||
secret-b (:secret payload)
|
||||
secret (b32/encode (.toByteArray secret-b))
|
||||
user (:name payload)
|
||||
issuer (:issuer payload)
|
||||
algorithm (case (:algorithmValuei payload) 2 "sha256" 3 "sha512" "sha1")
|
||||
digits (case (:digitsValue payload) 2 8 6)
|
||||
valid-type (= 2 (:typeValue payload))
|
||||
]
|
||||
(println "name:" name "user:" user "issuer:" issuer "digitsValue:" digits "algorithm:" algorithm "valid type?" valid-type)
|
||||
(if valid-type
|
||||
(create-app name secret user issuer algorithm digits 30)
|
||||
(println "Invalid OTP type" (:typeValue 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")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
(ns totp.db.datomic
|
||||
(:require [datomic.client.api :as d]))
|
||||
|
||||
(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"}))
|
||||
@@ -1,23 +0,0 @@
|
||||
(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)
|
||||
)
|
||||
BIN
target/classes/protoc/OtpauthMigration$MigrationPayload$1.class
Normal file
BIN
target/classes/protoc/OtpauthMigration$MigrationPayload$1.class
Normal file
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
target/classes/protoc/OtpauthMigration$MigrationPayload.class
Normal file
BIN
target/classes/protoc/OtpauthMigration$MigrationPayload.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
target/classes/protoc/OtpauthMigration.class
Normal file
BIN
target/classes/protoc/OtpauthMigration.class
Normal file
Binary file not shown.
@@ -1,4 +1,5 @@
|
||||
(ns totp.core-test
|
||||
#_{:clj-kondo/ignore [:refer-all]}
|
||||
(:require [clojure.test :refer :all]
|
||||
[totp.core :refer :all]
|
||||
[alphabase.base64 :as b64])
|
||||
|
||||
BIN
totp-data.sqlite
BIN
totp-data.sqlite
Binary file not shown.
Reference in New Issue
Block a user