Add account management web pages (#694)

Added internal/frontend/account to implement account management code. 
Added user registration templates and handlers.
Set up a sqlite and postgres database in package main.
This commit is contained in:
Victor Vrantchan
2020-07-25 20:34:59 -04:00
committed by GitHub
parent cf94d41d9b
commit 1d698cd23e
17 changed files with 524 additions and 17 deletions

View File

@@ -60,10 +60,6 @@ func (u *User) ValidatePassword(plaintext string) error {
}
func (u *User) setPassword(plaintext string) error {
if plaintext == "" {
return errors.New("password cannot be empty")
}
salt, err := random(32, base64.StdEncoding)
if err != nil {
return err
@@ -93,6 +89,24 @@ func random(keySize int, enc *base64.Encoding) ([]byte, error) {
// DB
func create(username, email, password string) (*User, error) {
val := Error{invalid: make(map[string]string)}
if username == "" {
val.invalid["username"] = constraints["chk_username_not_empty"]["username"]
}
if email == "" {
val.invalid["email"] = constraints["chk_email_not_empty"]["email"]
}
if password == "" {
val.invalid["password"] = constraints["chk_password_not_empty"]["password"]
}
if len(val.invalid) > 0 {
return nil, val
}
u := &User{
ID: id.New(),
Username: username,
@@ -112,3 +126,38 @@ func create(username, email, password string) (*User, error) {
return u, nil
}
type Error struct {
invalid map[string]string
}
func (err Error) Invalid() map[string]string { return err.invalid }
func (err Error) Error() string {
switch len(err.invalid) {
case 0:
return "user validation failed"
case 1:
var key, value string
for k, v := range err.invalid {
key = k
value = v
break
}
return fmt.Sprintf("user validation failed: %s - %s", key, value)
default:
var key, value string
for k, v := range err.invalid {
key = k
value = v
break
}
return fmt.Sprintf("user validation failed: %s - %s and %d other errors", key, value, len(err.invalid)-1)
}
}
var constraints = map[string]map[string]string{
"chk_email_not_empty": map[string]string{"email": "You must provide an email address."},
"chk_username_not_empty": map[string]string{"username": "You must provide a username."},
"chk_password_not_empty": map[string]string{"password": "You must provide a password."},
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"github.com/jackc/pgconn"
"github.com/jackc/pgx/v4/pgxpool"
)
@@ -38,7 +39,7 @@ func (d *Postgres) CreateUser(ctx context.Context, username, email, password str
u.Salt,
u.ConfirmationHash,
).Scan(&u.CreatedAt, &u.UpdatedAt); err != nil {
return nil, fmt.Errorf("store created user in postgres: %w", err)
return nil, fmt.Errorf("store created user in postgres: %w", checkPostgres(err))
}
return u, nil
@@ -58,3 +59,19 @@ func (d *Postgres) ConfirmUser(ctx context.Context, confirmation string) error {
return nil
}
func checkPostgres(err error) error {
var dbErr *pgconn.PgError
if !errors.As(err, &dbErr) {
return err
}
switch dbErr.Code {
case "23514":
if kv, ok := constraints[dbErr.ConstraintName]; ok {
return Error{invalid: kv}
}
}
return err
}

View File

@@ -4,8 +4,10 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"
"crawshaw.io/sqlite"
"crawshaw.io/sqlite/sqlitex"
)
@@ -42,7 +44,7 @@ func (d *SQLite) CreateUser(ctx context.Context, username, email, password strin
stmt.SetBytes("$salt", u.Salt)
stmt.SetText("$confirmationHash", *u.ConfirmationHash)
if _, err := stmt.Step(); err != nil {
return nil, err
return nil, fmt.Errorf("store created user in sqlite: %w", checkSqlite(err))
}
stmt = conn.Prep(`SELECT created_at, updated_at FROM users WHERE id = $id`)
@@ -87,3 +89,20 @@ func (d *SQLite) ConfirmUser(ctx context.Context, confirmation string) error {
return nil
}
func checkSqlite(err error) error {
var sErr sqlite.Error
if !errors.As(err, &sErr) {
return err
}
switch sErr.Code {
case sqlite.SQLITE_CONSTRAINT_CHECK:
c := strings.Split(sErr.Msg, ": ")
if kv, ok := constraints[c[len(c)-1]]; ok {
return Error{invalid: kv}
}
}
return fmt.Errorf("user: %w", err)
}