Last active
February 4, 2025 18:11
-
-
Save alexaandru/88b529fb1474646432812f036633fe37 to your computer and use it in GitHub Desktop.
OTP cli
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[email protected] namespaces="git" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCx0x2FqQPAESrRfU8+TqqikuqB1F4OLDMkGdiZzsN19/59lLGPsZgQfejjHiujCWPRb0Tpz2Jv2mB6QcUVuK/gfiMbltwCdcDNCCa0I42xOgIHoYVatry7D8mZcy+t+e1TALrEUPCod4BEn8oLLnsGy6siDzv2hWINbIs2XLCvsOisa6xERgM1Joezo5+8IshSeP9felv6PqbfKEUWV0zfsoLbxJ0WAAM8T14OA/GIm12Dmg4fNWad70Bp1Yq/0W00vpZ9up+p8kMklZYlK6QHwrIw2gzVqsIL9x3FXgVw7KFRfV1Gp1W7V2S2sumzUW/BIztA0JOWqZnd0CMsSEyX |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Install with: | |
// export X=88b529fb1474646432812f036633fe37.git | |
// go install gist.github.com/alexaandru/$X@latest && mv $(which $X) $(dirname $(which $X))/otp | |
module gist.github.com/alexaandru/88b529fb1474646432812f036633fe37.git | |
go 1.23.3 | |
require github.com/pquerna/otp v1.4.0 | |
require github.com/boombuler/barcode v1.0.2 // indirect | |
retract v1.1.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= | |
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4= | |
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= | |
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | |
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= | |
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= | |
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= | |
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
install: | |
@go install . | |
@export X=88b529fb1474646432812f036633fe37.git; mv $$(which $$X) $$(dirname $$(which $$X))/otp; \ | |
ln -sf $$(which otp) $$(dirname $$(which otp))/code; \ | |
ln -sf $$(which otp) $$(dirname $$(which otp))/verify |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"cmp" | |
"encoding/json" | |
"errors" | |
"fmt" | |
"maps" | |
"os" | |
"os/exec" | |
"path/filepath" | |
"slices" | |
"strings" | |
"time" | |
"github.com/pquerna/otp/totp" | |
) | |
type Key struct { | |
Username, | |
Secret, | |
Issuer, | |
Period, | |
Algorithm, | |
Digits string | |
} | |
type Keys map[string]Key | |
var ( | |
otpFile = cmp.Or(os.Getenv("OTP_FILE"), "~/Personal/Other/otp.json") | |
keys = Keys{} | |
cmd, scope, target string | |
) | |
func clipboardWrite(s string) { | |
cmd := exec.Command("bash", "-c", fmt.Sprintf("echo %s|xclip", s)) | |
if err := cmd.Run(); err != nil { | |
fmt.Println("WARN: could not copy to clipboard:", err) | |
} | |
} | |
func code(scope string) (err error) { | |
k := keys[scope] | |
code, err := totp.GenerateCode(k.Secret, time.Now()) | |
if err != nil { | |
return | |
} | |
clipboardWrite(code) | |
fmt.Println(scope, "OK") | |
return | |
} | |
func validate(scope, target string) { | |
k := keys[scope] | |
fmt.Println(totp.Validate(target, k.Secret)) | |
} | |
func main() { | |
if cmd != "export" && cmd != "complete" { | |
if _, ok := keys[scope]; !ok { | |
die("secret not available: " + scope) | |
} | |
} | |
switch cmd { | |
case "code": | |
die(code(scope)) | |
case "verify": | |
validate(scope, target) | |
case "export": | |
keys.export() | |
case "complete": | |
keys.completion() | |
default: | |
die("unknown command: " + cmd) | |
} | |
} | |
func (kx Keys) completion() { | |
fmt.Println(strings.Join(slices.Collect(maps.Keys(kx)), " ")) | |
} | |
func parseCmdline() error { | |
args := slices.Clone(os.Args) | |
self := filepath.Base(os.Args[0]) | |
if self == "code" || self == "verify" { | |
args = slices.Concat([]string{self, self}, args[1:]) | |
} | |
if len(args) == 2 && (args[1] == "export" || args[1] == "complete") { | |
cmd = args[1] | |
return nil | |
} | |
if len(args) < 3 { | |
return errors.New("must pass a command and a target/scope") | |
} | |
cmd, scope = args[1], args[2] | |
if cmd == "verify" { | |
if len(args) < 4 { | |
return errors.New("verify requires a code") | |
} else { | |
target = args[3] | |
} | |
} | |
return nil | |
} | |
func die(err any) { | |
if err == nil { | |
return | |
} | |
fmt.Println("ERROR:", err) | |
os.Exit(1) | |
} | |
func init() { | |
if strings.HasPrefix(otpFile, "~/") { | |
home, _ := os.UserHomeDir() | |
otpFile = filepath.Join(home, otpFile[2:]) | |
} | |
content, err := os.ReadFile(otpFile) | |
die(err) | |
var keys_ []Key | |
die(json.Unmarshal(content, &keys_)) | |
for _, k := range keys_ { | |
keys[k.Username] = k | |
} | |
die(parseCmdline()) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"net/url" | |
) | |
func (kx Keys) export() { | |
for _, k := range keys { | |
fmt.Println(k.export()) | |
} | |
} | |
func (k Key) export() string { | |
return fmt.Sprintf("otpauth://totp/%s?secret=%s&algorithm=%s&digits=%s&period=%s", | |
url.QueryEscape(k.Username+":"+k.Issuer), k.Secret, k.Algorithm, k.Digits, k.Period) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment