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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.handmade.network/hmn/hmn/src/oops"
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
"github.com/jackc/pgx/v4/pgxpool"
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"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)
|
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 {
|
type Argon2idConfig struct {
|
||||||
Time uint32
|
Time uint32
|
||||||
Memory uint32
|
Memory uint32
|
||||||
|
@ -152,7 +159,7 @@ func HashPassword(password string) (HashedPassword, error) {
|
||||||
|
|
||||||
cfg := Argon2idConfig{
|
cfg := Argon2idConfig{
|
||||||
Time: 1,
|
Time: 1,
|
||||||
Memory: 40 * 1024 * 1024,
|
Memory: 40 * 1024, // this is in KiB for some reason
|
||||||
Threads: 1,
|
Threads: 1,
|
||||||
KeyLength: keyLength,
|
KeyLength: keyLength,
|
||||||
}
|
}
|
||||||
|
@ -167,3 +174,16 @@ func HashPassword(password string) (HashedPassword, error) {
|
||||||
Hash: keyEnc,
|
Hash: keyEnc,
|
||||||
}, nil
|
}, 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",
|
Short: "Create a new database migration file",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if len(args) < 2 {
|
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)
|
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 {
|
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)
|
session, err := auth.CreateSession(c.Context(), s.conn, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Errored(http.StatusInternalServerError, oops.New(err, "failed to create session"))
|
c.Errored(http.StatusInternalServerError, oops.New(err, "failed to create session"))
|
||||||
|
|
Loading…
Reference in New Issue