From fff0b27a95c0f71f360282b68efe7d75203c1e7e Mon Sep 17 00:00:00 2001 From: MrMelon54 Date: Sun, 12 May 2024 15:11:38 +0100 Subject: [PATCH] Make a working test program --- auth.go | 88 +++++++++++++++++++++++++++++++++++++ database/auth.sql.go | 24 ++++++++++ database/db.go | 31 +++++++++++++ database/models.go | 13 ++++++ database/models/mailbox.sql | 6 +++ database/queries/auth.sql | 5 +++ go.sum | 55 +++++++++++++++++++++++ logger.go | 47 ++++++++++++++++++++ 8 files changed, 269 insertions(+) create mode 100644 auth.go create mode 100644 database/auth.sql.go create mode 100644 database/db.go create mode 100644 database/models.go create mode 100644 database/models/mailbox.sql create mode 100644 database/queries/auth.sql create mode 100644 go.sum create mode 100644 logger.go diff --git a/auth.go b/auth.go new file mode 100644 index 0000000..62b3919 --- /dev/null +++ b/auth.go @@ -0,0 +1,88 @@ +package cardcaldav + +import ( + "cardcaldav/database" + "context" + "errors" + "golang.org/x/crypto/bcrypt" + "net/http" + "strings" + + _ "github.com/go-sql-driver/mysql" +) + +type contextKey int + +var authCtxKey contextKey = 0 + +type Context struct { + UserName string +} + +func NewContext(ctx context.Context, a *Context) context.Context { + return context.WithValue(ctx, authCtxKey, a) +} + +func FromContext(ctx context.Context) (*Context, bool) { + a, ok := ctx.Value(authCtxKey).(*Context) + return a, ok +} + +type ProviderMiddleware interface { + Middleware(next http.Handler) http.Handler +} + +var authError = errors.New("auth context error") + +type Auth struct { + DB *database.Queries +} + +func (a *Auth) Middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Authorization") == "" { + w.Header().Add("WWW-Authenticate", `Basic realm="Please provide a password", charset="UTF-8"`) + http.Error(w, "HTTP auth is required", http.StatusUnauthorized) + return + } + username, accessToken, ok := r.BasicAuth() + if !ok { + http.Error(w, "Authorization invalid", http.StatusUnauthorized) + return + } + + // validate username and password + if a.ValidateCredentials(r.Context(), username, accessToken) != nil { + http.Error(w, "Authorization invalid", http.StatusUnauthorized) + return + } + + r = r.WithContext(NewContext(r.Context(), &Context{UserName: username})) + r.BasicAuth() + next.ServeHTTP(w, r) + }) +} + +func (a *Auth) CurrentUserPrincipal(ctx context.Context) (string, error) { + authCtx, ok := FromContext(ctx) + if !ok { + return "", authError + } + return "/" + authCtx.UserName + "/", nil +} + +const blfCryptPrefix = "{BLF-CRYPT}" + +var errNotBlfCrypt = errors.New("not BLF crypt") + +func (a *Auth) ValidateCredentials(ctx context.Context, un, pw string) error { + hash, err := a.DB.GetPasswordHash(ctx, un) + if err != nil { + return err + } + if !strings.HasPrefix(hash, blfCryptPrefix) { + return errNotBlfCrypt + } + hash = hash[len(blfCryptPrefix):] + return bcrypt.CompareHashAndPassword([]byte(hash), []byte(pw)) +} diff --git a/database/auth.sql.go b/database/auth.sql.go new file mode 100644 index 0000000..573258a --- /dev/null +++ b/database/auth.sql.go @@ -0,0 +1,24 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.25.0 +// source: auth.sql + +package database + +import ( + "context" +) + +const getPasswordHash = `-- name: GetPasswordHash :one +SELECT password +FROM mailbox +WHERE username = ? + AND active > 0 +` + +func (q *Queries) GetPasswordHash(ctx context.Context, username string) (string, error) { + row := q.db.QueryRowContext(ctx, getPasswordHash, username) + var password string + err := row.Scan(&password) + return password, err +} diff --git a/database/db.go b/database/db.go new file mode 100644 index 0000000..61f5bf4 --- /dev/null +++ b/database/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.25.0 + +package database + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/database/models.go b/database/models.go new file mode 100644 index 0000000..9a112fe --- /dev/null +++ b/database/models.go @@ -0,0 +1,13 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.25.0 + +package database + +import () + +type Mailbox struct { + Username string `json:"username"` + Password string `json:"password"` + Active int32 `json:"active"` +} diff --git a/database/models/mailbox.sql b/database/models/mailbox.sql new file mode 100644 index 0000000..12c164f --- /dev/null +++ b/database/models/mailbox.sql @@ -0,0 +1,6 @@ +CREATE TABLE mailbox +( + username VARCHAR(254) PRIMARY KEY UNIQUE NOT NULL, + password VARCHAR(256) NOT NULL, + active INT NOT NULL +); diff --git a/database/queries/auth.sql b/database/queries/auth.sql new file mode 100644 index 0000000..71c33b0 --- /dev/null +++ b/database/queries/auth.sql @@ -0,0 +1,5 @@ +-- name: GetPasswordHash :one +SELECT password +FROM mailbox +WHERE username = ? + AND active > 0; diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..38bcfc3 --- /dev/null +++ b/go.sum @@ -0,0 +1,55 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= +github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..8c0003e --- /dev/null +++ b/logger.go @@ -0,0 +1,47 @@ +package cardcaldav + +import ( + "github.com/charmbracelet/log" + "github.com/rs/zerolog" + log0 "github.com/rs/zerolog/log" + "strings" +) + +type zeroToCharmLogger struct{ logger *log.Logger } + +func (z *zeroToCharmLogger) Write(p []byte) (n int, err error) { + s := string(p) + if len(s) <= 3 { + return len(p), nil + } + inLevel := s[:3] + var level log.Level + switch inLevel { + case "TRC", "DBG": + level = log.DebugLevel + case "INF": + level = log.InfoLevel + case "WRN": + level = log.WarnLevel + case "ERR": + level = log.ErrorLevel + case "FTL", "PNC": + level = log.FatalLevel + } + z.logger.Helper() + translator := z.logger.With() + translator.SetCallerFormatter(func(s string, i int, s2 string) string { + return "tokidoki internal" + }) + translator.Log(level, strings.TrimSpace(s[4:])) + return len(p), nil +} + +func SetupLogger(logger *log.Logger) { + log0.Logger = log0.Output(zerolog.ConsoleWriter{ + Out: &zeroToCharmLogger{logger}, + FormatTimestamp: func(i interface{}) string { + return "" + }, + }) +}