diff --git a/src/auth/auth.go b/src/auth/auth.go index 5a11ade..6806c83 100644 --- a/src/auth/auth.go +++ b/src/auth/auth.go @@ -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 +} diff --git a/src/migration/migration.go b/src/migration/migration.go index 4eb0bfd..60e9a0b 100644 --- a/src/migration/migration.go +++ b/src/migration/migration.go @@ -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) } diff --git a/src/migration/migrations/2021-03-28T152630Z_AllowLongerPasswordHashes.go b/src/migration/migrations/2021-03-28T152630Z_AllowLongerPasswordHashes.go new file mode 100644 index 0000000..2b22819 --- /dev/null +++ b/src/migration/migrations/2021-03-28T152630Z_AllowLongerPasswordHashes.go @@ -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 +} diff --git a/src/website/routes.go b/src/website/routes.go index be500ca..abba5ca 100644 --- a/src/website/routes.go +++ b/src/website/routes.go @@ -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"))