Re-hash user's passwords if necessary when they log in
This commit is contained in:
parent
8f2958594a
commit
f7ac023c44
|
@ -2,15 +2,18 @@ package auth
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/oops"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
@ -54,6 +57,10 @@ func (p HashedPassword) String() string {
|
|||
return fmt.Sprintf("%s$%s$%s$%s", p.Algorithm, p.AlgoConfig, p.Salt, p.Hash)
|
||||
}
|
||||
|
||||
func (p HashedPassword) IsOutdated() bool {
|
||||
return p.Algorithm != Argon2id
|
||||
}
|
||||
|
||||
type Argon2idConfig struct {
|
||||
Time uint32
|
||||
Memory uint32
|
||||
|
@ -152,7 +159,7 @@ func HashPassword(password string) (HashedPassword, error) {
|
|||
|
||||
cfg := Argon2idConfig{
|
||||
Time: 1,
|
||||
Memory: 40 * 1024 * 1024,
|
||||
Memory: 40 * 1024, // this is in KiB for some reason
|
||||
Threads: 1,
|
||||
KeyLength: keyLength,
|
||||
}
|
||||
|
@ -167,3 +174,16 @@ func HashPassword(password string) (HashedPassword, error) {
|
|||
Hash: keyEnc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var ErrUserDoesNotExist = errors.New("user does not exist")
|
||||
|
||||
func UpdatePassword(ctx context.Context, conn *pgxpool.Pool, username string, hp HashedPassword) error {
|
||||
tag, err := conn.Exec(ctx, "UPDATE auth_user SET password = $1 WHERE username = $2", hp.String(), username)
|
||||
if err != nil {
|
||||
return oops.New(err, "failed to update password")
|
||||
} else if tag.RowsAffected() < 1 {
|
||||
return ErrUserDoesNotExist
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -49,7 +49,8 @@ func init() {
|
|||
Short: "Create a new database migration file",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) < 2 {
|
||||
fmt.Println("You must provide a name and a description.")
|
||||
fmt.Printf("You must provide a name and a description.\n\n")
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/migration/types"
|
||||
"github.com/jackc/pgx/v4"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerMigration(AllowLongerPasswordHashes{})
|
||||
}
|
||||
|
||||
type AllowLongerPasswordHashes struct{}
|
||||
|
||||
func (m AllowLongerPasswordHashes) Version() types.MigrationVersion {
|
||||
return types.MigrationVersion(time.Date(2021, 3, 28, 15, 26, 30, 0, time.UTC))
|
||||
}
|
||||
|
||||
func (m AllowLongerPasswordHashes) Name() string {
|
||||
return "AllowLongerPasswordHashes"
|
||||
}
|
||||
|
||||
func (m AllowLongerPasswordHashes) Description() string {
|
||||
return "Increase the storage size limit on hashed passwords"
|
||||
}
|
||||
|
||||
func (m AllowLongerPasswordHashes) Up(tx pgx.Tx) error {
|
||||
_, err := tx.Exec(context.Background(), `
|
||||
ALTER TABLE auth_user
|
||||
ALTER COLUMN password TYPE VARCHAR(256)
|
||||
`)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m AllowLongerPasswordHashes) Down(tx pgx.Tx) error {
|
||||
_, err := tx.Exec(context.Background(), `
|
||||
ALTER TABLE auth_user
|
||||
ALTER COLUMN password TYPE VARCHAR(128)
|
||||
`)
|
||||
return err
|
||||
}
|
|
@ -183,6 +183,20 @@ func (s *websiteRoutes) Login(c *RequestContext, p httprouter.Params) {
|
|||
}
|
||||
|
||||
if passwordsMatch {
|
||||
// re-hash and save the user's password if necessary
|
||||
if hashed.IsOutdated() {
|
||||
newHashed, err := auth.HashPassword(password)
|
||||
if err == nil {
|
||||
err := auth.UpdatePassword(c.Context(), s.conn, username, newHashed)
|
||||
if err != nil {
|
||||
c.Logger.Error().Err(err).Msg("failed to update user's password")
|
||||
}
|
||||
} else {
|
||||
c.Logger.Error().Err(err).Msg("failed to re-hash password")
|
||||
}
|
||||
// If errors happen here, we can still continue with logging them in
|
||||
}
|
||||
|
||||
session, err := auth.CreateSession(c.Context(), s.conn, username)
|
||||
if err != nil {
|
||||
c.Errored(http.StatusInternalServerError, oops.New(err, "failed to create session"))
|
||||
|
|
Loading…
Reference in New Issue