Initial implementation of password-checking
This commit is contained in:
parent
4fb161b3c6
commit
acca4fe232
1
go.mod
1
go.mod
|
@ -18,6 +18,7 @@ require (
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/teacat/noire v1.1.0
|
github.com/teacat/noire v1.1.0
|
||||||
github.com/wellington/go-libsass v0.9.2
|
github.com/wellington/go-libsass v0.9.2
|
||||||
|
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/rs/zerolog v1.20.0 => github.com/bvisness/zerolog v1.20.1-0.20210321191248-05f63bf0e9e0
|
replace github.com/rs/zerolog v1.20.0 => github.com/bvisness/zerolog v1.20.1-0.20210321191248-05f63bf0e9e0
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -304,8 +304,9 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 h1:gzMM0EjIYiRmJI3+jBdFuoynZlpxa2JQZsolKu09BXo=
|
||||||
|
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
@ -340,8 +341,9 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -372,7 +374,9 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HashAlgorithm string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PBKDF2_SHA256 = "pbkdf2_sha256"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PKBDF2KeyLength = 64
|
||||||
|
|
||||||
|
type HashedPassword struct {
|
||||||
|
Algorithm HashAlgorithm
|
||||||
|
Iterations int
|
||||||
|
Salt string
|
||||||
|
Hash string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseDjangoPasswordString(s string) (HashedPassword, error) {
|
||||||
|
pieces := strings.SplitN(s, "$", 4)
|
||||||
|
if len(pieces) < 4 {
|
||||||
|
return HashedPassword{}, oops.New(nil, "unrecognized password string format")
|
||||||
|
}
|
||||||
|
|
||||||
|
iterations, err := strconv.Atoi(pieces[1])
|
||||||
|
if err != nil {
|
||||||
|
return HashedPassword{}, oops.New(err, "could not parse password iterations")
|
||||||
|
}
|
||||||
|
|
||||||
|
return HashedPassword{
|
||||||
|
Algorithm: HashAlgorithm(pieces[0]),
|
||||||
|
Iterations: iterations,
|
||||||
|
Salt: pieces[2],
|
||||||
|
Hash: pieces[3],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckPassword(password string, hashedPassword HashedPassword) (bool, error) {
|
||||||
|
switch hashedPassword.Algorithm {
|
||||||
|
case PBKDF2_SHA256:
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(hashedPassword.Hash)
|
||||||
|
if err != nil {
|
||||||
|
return false, oops.New(nil, "failed to get key length of hashed password")
|
||||||
|
}
|
||||||
|
|
||||||
|
newHash := pbkdf2.Key(
|
||||||
|
[]byte(password),
|
||||||
|
[]byte(hashedPassword.Salt),
|
||||||
|
hashedPassword.Iterations,
|
||||||
|
len(decoded),
|
||||||
|
sha256.New,
|
||||||
|
)
|
||||||
|
newHashEncoded := base64.StdEncoding.EncodeToString(newHash)
|
||||||
|
|
||||||
|
return bytes.Equal([]byte(newHashEncoded), []byte(hashedPassword.Hash)), nil
|
||||||
|
default:
|
||||||
|
return false, oops.New(nil, "unrecognized password hash algorithm: %s", hashedPassword.Algorithm)
|
||||||
|
}
|
||||||
|
}
|
20
src/db/db.go
20
src/db/db.go
|
@ -3,11 +3,11 @@ package db
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.handmade.network/hmn/hmn/src/config"
|
"git.handmade.network/hmn/hmn/src/config"
|
||||||
"git.handmade.network/hmn/hmn/src/models"
|
|
||||||
"git.handmade.network/hmn/hmn/src/oops"
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
"github.com/jackc/pgx/v4"
|
"github.com/jackc/pgx/v4"
|
||||||
"github.com/jackc/pgx/v4/log/zerologadapter"
|
"github.com/jackc/pgx/v4/log/zerologadapter"
|
||||||
|
@ -61,11 +61,19 @@ func (it *StructQueryIterator) Next(dest interface{}) bool {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%#v\n", vals)
|
||||||
|
|
||||||
for i, val := range vals {
|
for i, val := range vals {
|
||||||
field := v.Elem().Field(it.fieldIndices[i])
|
field := v.Elem().Field(it.fieldIndices[i])
|
||||||
switch field.Kind() {
|
switch field.Kind() {
|
||||||
case reflect.Int:
|
case reflect.Int:
|
||||||
field.SetInt(reflect.ValueOf(val).Int())
|
field.SetInt(reflect.ValueOf(val).Int())
|
||||||
|
case reflect.Ptr:
|
||||||
|
// TODO: I'm pretty sure we don't handle nullable ints correctly lol. Maybe this needs to be a function somehow, and recurse onto itself?? Reflection + recursion sounds like a great idea
|
||||||
|
if val != nil {
|
||||||
|
field.Set(reflect.New(field.Type().Elem()))
|
||||||
|
field.Elem().Set(reflect.ValueOf(val))
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
field.Set(reflect.ValueOf(val))
|
field.Set(reflect.ValueOf(val))
|
||||||
}
|
}
|
||||||
|
@ -82,7 +90,15 @@ func QueryToStructs(ctx context.Context, conn *pgxpool.Pool, destType interface{
|
||||||
var fieldIndices []int
|
var fieldIndices []int
|
||||||
var columnNames []string
|
var columnNames []string
|
||||||
|
|
||||||
t := reflect.TypeOf(models.Project{})
|
t := reflect.TypeOf(destType)
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return StructQueryIterator{}, oops.New(nil, "QueryToStructs requires a struct type or a pointer to a struct type")
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
f := t.Field(i)
|
f := t.Field(i)
|
||||||
if columnName := f.Tag.Get("db"); columnName != "" {
|
if columnName := f.Tag.Get("db"); columnName != "" {
|
||||||
|
|
|
@ -185,8 +185,15 @@ func LogPanicValue(logger *zerolog.Logger, val interface{}, msg string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err, ok := val.(error); ok {
|
if err, ok := val.(error); ok {
|
||||||
logger.Error().Err(err).Msg(msg)
|
l := logger.Error().Err(err)
|
||||||
|
if _, ok := err.(*oops.Error); !ok {
|
||||||
|
l = l.Interface(zerolog.ErrorStackFieldName, oops.Trace())
|
||||||
|
}
|
||||||
|
l.Msg(msg)
|
||||||
} else {
|
} else {
|
||||||
logger.Error().Interface("recovered", val).Msg(msg)
|
logger.Error().
|
||||||
|
Interface("recovered", val).
|
||||||
|
Interface(zerolog.ErrorStackFieldName, oops.Trace()).
|
||||||
|
Msg(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID int `db:"id"`
|
||||||
|
|
||||||
|
Username string `db:"username"`
|
||||||
|
Password string `db:"password"`
|
||||||
|
Email string `db:"email"`
|
||||||
|
|
||||||
|
DateJoined time.Time `db:"date_joined"`
|
||||||
|
LastLogin *time.Time `db:"last_login"`
|
||||||
|
|
||||||
|
IsSuperuser bool `db:"is_superuser"`
|
||||||
|
IsStaff bool `db:"is_staff"`
|
||||||
|
IsActive bool `db:"is_active"`
|
||||||
|
}
|
|
@ -50,7 +50,15 @@ var ZerologStackMarshaler = func(err error) interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(wrapped error, format string, args ...interface{}) error {
|
func New(wrapped error, format string, args ...interface{}) error {
|
||||||
trace := stack.Trace().TrimRuntime()
|
return &Error{
|
||||||
|
Message: fmt.Sprintf(format, args...),
|
||||||
|
Wrapped: wrapped,
|
||||||
|
Stack: Trace(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Trace() CallStack {
|
||||||
|
trace := stack.Trace().TrimRuntime()[1:]
|
||||||
frames := make(CallStack, len(trace))
|
frames := make(CallStack, len(trace))
|
||||||
for i, call := range trace {
|
for i, call := range trace {
|
||||||
callFrame := call.Frame()
|
callFrame := call.Frame()
|
||||||
|
@ -61,9 +69,5 @@ func New(wrapped error, format string, args ...interface{}) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Error{
|
return frames
|
||||||
Message: fmt.Sprintf(format, args...),
|
|
||||||
Wrapped: wrapped,
|
|
||||||
Stack: frames,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,11 @@ func (r *HMNRouter) GET(route string, handler HMNHandler) {
|
||||||
r.Handle(http.MethodGet, route, handler)
|
r.Handle(http.MethodGet, route, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: POST, etc.
|
func (r *HMNRouter) POST(route string, handler HMNHandler) {
|
||||||
|
r.Handle(http.MethodPost, route, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: More methods
|
||||||
|
|
||||||
func (r *HMNRouter) ServeFiles(path string, root http.FileSystem) {
|
func (r *HMNRouter) ServeFiles(path string, root http.FileSystem) {
|
||||||
r.HttpRouter.ServeFiles(path, root)
|
r.HttpRouter.ServeFiles(path, root)
|
||||||
|
@ -94,7 +98,7 @@ func (c *RequestContext) AddErrors(errs ...error) {
|
||||||
c.Errors = append(c.Errors, errs...)
|
c.Errors = append(c.Errors, errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RequestContext) AbortWithErrors(status int, errs ...error) {
|
func (c *RequestContext) Errored(status int, errs ...error) {
|
||||||
c.StatusCode = status
|
c.StatusCode = status
|
||||||
c.AddErrors(errs...)
|
c.AddErrors(errs...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,16 @@ package website
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/auth"
|
||||||
"git.handmade.network/hmn/hmn/src/db"
|
"git.handmade.network/hmn/hmn/src/db"
|
||||||
|
"git.handmade.network/hmn/hmn/src/logging"
|
||||||
"git.handmade.network/hmn/hmn/src/models"
|
"git.handmade.network/hmn/hmn/src/models"
|
||||||
"git.handmade.network/hmn/hmn/src/oops"
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
"git.handmade.network/hmn/hmn/src/templates"
|
"git.handmade.network/hmn/hmn/src/templates"
|
||||||
|
@ -23,8 +27,11 @@ type websiteRoutes struct {
|
||||||
|
|
||||||
func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
|
func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
|
||||||
routes := &websiteRoutes{
|
routes := &websiteRoutes{
|
||||||
HMNRouter: &HMNRouter{HttpRouter: httprouter.New()},
|
HMNRouter: &HMNRouter{
|
||||||
conn: conn,
|
HttpRouter: httprouter.New(),
|
||||||
|
Wrappers: []HMNHandlerWrapper{ErrorLoggingWrapper},
|
||||||
|
},
|
||||||
|
conn: conn,
|
||||||
}
|
}
|
||||||
|
|
||||||
mainRoutes := routes.WithWrappers(routes.CommonWebsiteDataWrapper)
|
mainRoutes := routes.WithWrappers(routes.CommonWebsiteDataWrapper)
|
||||||
|
@ -32,6 +39,8 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
|
||||||
mainRoutes.GET("/project/:id", routes.Project)
|
mainRoutes.GET("/project/:id", routes.Project)
|
||||||
mainRoutes.GET("/assets/project.css", routes.ProjectCSS)
|
mainRoutes.GET("/assets/project.css", routes.ProjectCSS)
|
||||||
|
|
||||||
|
routes.POST("/login", routes.Login)
|
||||||
|
|
||||||
routes.ServeFiles("/public/*filepath", http.Dir("public"))
|
routes.ServeFiles("/public/*filepath", http.Dir("public"))
|
||||||
|
|
||||||
return routes
|
return routes
|
||||||
|
@ -120,6 +129,63 @@ func (s *websiteRoutes) ProjectCSS(c *RequestContext, p httprouter.Params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *websiteRoutes) Login(c *RequestContext, p httprouter.Params) {
|
||||||
|
bodyBytes, _ := io.ReadAll(c.Req.Body)
|
||||||
|
|
||||||
|
// TODO: Update this endpoint to give uniform responses on errors and to be resilient to timing attacks.
|
||||||
|
|
||||||
|
var body struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(bodyBytes, &body)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var user models.User
|
||||||
|
err = db.QueryOneToStruct(c.Context(), s.conn, &user, "SELECT $columns FROM auth_user WHERE username = $1", body.Username)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, db.ErrNoMatchingRows) {
|
||||||
|
c.StatusCode = http.StatusUnauthorized
|
||||||
|
} else {
|
||||||
|
c.Errored(http.StatusInternalServerError, oops.New(err, "failed to look up user by username"))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.Debug().Interface("user", user).Msg("the user to check")
|
||||||
|
|
||||||
|
hashed, err := auth.ParseDjangoPasswordString(user.Password)
|
||||||
|
if err != nil {
|
||||||
|
c.Errored(http.StatusInternalServerError, oops.New(err, "failed to parse password string"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordsMatch, err := auth.CheckPassword(body.Password, hashed)
|
||||||
|
if err != nil {
|
||||||
|
c.Errored(http.StatusInternalServerError, oops.New(err, "failed to check password against hash"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if passwordsMatch {
|
||||||
|
c.Body.WriteString("ur good")
|
||||||
|
} else {
|
||||||
|
c.StatusCode = http.StatusUnauthorized
|
||||||
|
c.Body.WriteString("nope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorLoggingWrapper(h HMNHandler) HMNHandler {
|
||||||
|
return func(c *RequestContext, p httprouter.Params) {
|
||||||
|
h(c, p)
|
||||||
|
|
||||||
|
for _, err := range c.Errors {
|
||||||
|
c.Logger.Error().Err(err).Msg("error occurred during request")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *websiteRoutes) CommonWebsiteDataWrapper(h HMNHandler) HMNHandler {
|
func (s *websiteRoutes) CommonWebsiteDataWrapper(h HMNHandler) HMNHandler {
|
||||||
return func(c *RequestContext, p httprouter.Params) {
|
return func(c *RequestContext, p httprouter.Params) {
|
||||||
slug := ""
|
slug := ""
|
||||||
|
@ -130,7 +196,7 @@ func (s *websiteRoutes) CommonWebsiteDataWrapper(h HMNHandler) HMNHandler {
|
||||||
|
|
||||||
dbProject, err := FetchProjectBySlug(c.Context(), s.conn, slug)
|
dbProject, err := FetchProjectBySlug(c.Context(), s.conn, slug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithErrors(http.StatusInternalServerError, oops.New(err, "failed to fetch current project"))
|
c.Errored(http.StatusInternalServerError, oops.New(err, "failed to fetch current project"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue