fixed GetTOTP, started rough QR code implementation
GetTOTP handles TOTP method retrieval for confirmation and deletion. QR code implementation looks like it's gonna suck, so might end up using a library for this later.
This commit is contained in:
parent
ad39e68cd6
commit
1edc051ae2
|
@ -304,6 +304,12 @@ func totpConfirmHandler(app *model.AppState) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"TOTP:\n\tName: %s\n\tSecret: %s\n",
|
||||||
|
totp.Name,
|
||||||
|
totp.Secret,
|
||||||
|
)
|
||||||
|
|
||||||
confirmCode := controller.GenerateTOTP(totp.Secret, 0)
|
confirmCode := controller.GenerateTOTP(totp.Secret, 0)
|
||||||
if code != confirmCode {
|
if code != confirmCode {
|
||||||
confirmCodeOffset := controller.GenerateTOTP(totp.Secret, 1)
|
confirmCodeOffset := controller.GenerateTOTP(totp.Secret, 1)
|
||||||
|
@ -330,12 +336,11 @@ func totpDeleteHandler(app *model.AppState) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
name := r.URL.Path
|
if len(r.URL.Path) < 2 {
|
||||||
fmt.Printf("%s\n", name);
|
|
||||||
if len(name) == 0 {
|
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
name := r.URL.Path[1:]
|
||||||
|
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,17 @@ import (
|
||||||
func Handler(app *model.AppState) http.Handler {
|
func Handler(app *model.AppState) http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.Handle("/qr-test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
qrB64Img, err := controller.GenerateQRCode([]byte("super epic mega gaming test message. be sure to buy free2play on bandcamp so i can put food on my family"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "WARN: Failed to generate QR code: %v\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte("<html><img style=\"image-rendering:pixelated;width:100%;height:100%;object-fit:contain\" src=\"" + qrB64Img + "\"/></html>"))
|
||||||
|
}))
|
||||||
|
|
||||||
mux.Handle("/login", loginHandler(app))
|
mux.Handle("/login", loginHandler(app))
|
||||||
mux.Handle("/logout", requireAccount(app, logoutHandler(app)))
|
mux.Handle("/logout", requireAccount(app, logoutHandler(app)))
|
||||||
|
|
||||||
|
@ -243,11 +254,6 @@ func loginHandler(app *model.AppState) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// new accounts won't have TOTP methods at first. there should be a
|
|
||||||
// second phase of login that prompts the user for a TOTP *only*
|
|
||||||
// if that account has a TOTP method.
|
|
||||||
// TODO: login phases (username & password -> TOTP)
|
|
||||||
|
|
||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
|
107
controller/qr.go
Normal file
107
controller/qr.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
)
|
||||||
|
|
||||||
|
const margin = 4
|
||||||
|
|
||||||
|
type QRCodeECCLevel int64
|
||||||
|
const (
|
||||||
|
LOW QRCodeECCLevel = iota
|
||||||
|
MEDIUM
|
||||||
|
QUARTILE
|
||||||
|
HIGH
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateQRCode(data []byte) (string, error) {
|
||||||
|
version := 1
|
||||||
|
|
||||||
|
size := 0
|
||||||
|
size = 21 + version * 4
|
||||||
|
if version > 10 {
|
||||||
|
return "", errors.New(fmt.Sprintf("QR version %d not supported", version))
|
||||||
|
}
|
||||||
|
|
||||||
|
img := image.NewGray(image.Rect(0, 0, size + margin * 2, size + margin * 2))
|
||||||
|
|
||||||
|
// fill white
|
||||||
|
for y := range size + margin * 2 {
|
||||||
|
for x := range size + margin * 2 {
|
||||||
|
img.Set(x, y, color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw alignment squares
|
||||||
|
drawLargeAlignmentSquare(margin, margin, img)
|
||||||
|
drawLargeAlignmentSquare(margin, margin + size - 7, img)
|
||||||
|
drawLargeAlignmentSquare(margin + size - 7, margin, img)
|
||||||
|
drawSmallAlignmentSquare(size - 5, size - 5, img)
|
||||||
|
/*
|
||||||
|
if version > 4 {
|
||||||
|
space := version * 3 - 2
|
||||||
|
end := size / space
|
||||||
|
for y := range size / space + 1 {
|
||||||
|
for x := range size / space + 1 {
|
||||||
|
if x == 0 && y == 0 { continue }
|
||||||
|
if x == 0 && y == end { continue }
|
||||||
|
if x == end && y == 0 { continue }
|
||||||
|
if x == end && y == end { continue }
|
||||||
|
drawSmallAlignmentSquare(
|
||||||
|
x * space + margin + 4,
|
||||||
|
y * space + margin + 4,
|
||||||
|
img,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// draw timing bits
|
||||||
|
for i := margin + 6; i < size - 4; i++ {
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
img.Set(i, margin + 6, color.Black)
|
||||||
|
img.Set(margin + 6, i, color.Black)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img.Set(margin + 8, size - 4, color.Black)
|
||||||
|
|
||||||
|
var imgBuf bytes.Buffer
|
||||||
|
err := png.Encode(&imgBuf, img)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
base64Img := base64.StdEncoding.EncodeToString(imgBuf.Bytes())
|
||||||
|
|
||||||
|
return "data:image/png;base64," + base64Img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawLargeAlignmentSquare(x int, y int, img *image.Gray) {
|
||||||
|
for yi := range 7 {
|
||||||
|
for xi := range 7 {
|
||||||
|
if (xi == 0 || xi == 6) || (yi == 0 || yi == 6) {
|
||||||
|
img.Set(x + xi, y + yi, color.Black)
|
||||||
|
} else if (xi > 1 && xi < 5) && (yi > 1 && yi < 5) {
|
||||||
|
img.Set(x + xi, y + yi, color.Black)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawSmallAlignmentSquare(x int, y int, img *image.Gray) {
|
||||||
|
for yi := range 5 {
|
||||||
|
for xi := range 5 {
|
||||||
|
if (xi == 0 || xi == 4) || (yi == 0 || yi == 4) {
|
||||||
|
img.Set(x + xi, y + yi, color.Black)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img.Set(x + 2, y + 2, color.Black)
|
||||||
|
}
|
|
@ -64,9 +64,9 @@ func GenerateTOTPURI(username string, secret string) string {
|
||||||
query := url.Query()
|
query := url.Query()
|
||||||
query.Set("secret", secret)
|
query.Set("secret", secret)
|
||||||
query.Set("issuer", "arimelody.me")
|
query.Set("issuer", "arimelody.me")
|
||||||
query.Set("algorithm", "SHA1")
|
// query.Set("algorithm", "SHA1")
|
||||||
query.Set("digits", fmt.Sprintf("%d", TOTP_CODE_LENGTH))
|
// query.Set("digits", fmt.Sprintf("%d", TOTP_CODE_LENGTH))
|
||||||
query.Set("period", fmt.Sprintf("%d", TOTP_TIME_STEP))
|
// query.Set("period", fmt.Sprintf("%d", TOTP_TIME_STEP))
|
||||||
url.RawQuery = query.Encode()
|
url.RawQuery = query.Encode()
|
||||||
|
|
||||||
return url.String()
|
return url.String()
|
||||||
|
@ -116,8 +116,9 @@ func GetTOTP(db *sqlx.DB, accountID string, name string) (*model.TOTP, error) {
|
||||||
err := db.Get(
|
err := db.Get(
|
||||||
&totp,
|
&totp,
|
||||||
"SELECT * FROM totp " +
|
"SELECT * FROM totp " +
|
||||||
"WHERE account=$1",
|
"WHERE account=$1 AND name=$2",
|
||||||
accountID,
|
accountID,
|
||||||
|
name,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
|
|
2
main.go
2
main.go
|
@ -89,7 +89,6 @@ func main() {
|
||||||
}
|
}
|
||||||
username := os.Args[2]
|
username := os.Args[2]
|
||||||
totpName := os.Args[3]
|
totpName := os.Args[3]
|
||||||
secret := controller.GenerateTOTPSecret(controller.TOTP_SECRET_LENGTH)
|
|
||||||
|
|
||||||
account, err := controller.GetAccountByUsername(app.DB, username)
|
account, err := controller.GetAccountByUsername(app.DB, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -102,6 +101,7 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secret := controller.GenerateTOTPSecret(controller.TOTP_SECRET_LENGTH)
|
||||||
totp := model.TOTP {
|
totp := model.TOTP {
|
||||||
AccountID: account.ID,
|
AccountID: account.ID,
|
||||||
Name: totpName,
|
Name: totpName,
|
||||||
|
|
Loading…
Reference in a new issue