From f46487b01555ce88aa951ff2a54c837aa9893a3c Mon Sep 17 00:00:00 2001 From: Bogdan Artemenko Date: Mon, 13 May 2019 18:53:52 +0300 Subject: [PATCH] ResetPassword Table and all CRUD methods. (#1916) --- .../console/consoleweb/consoleql/mail.go | 9 +- .../console/consoleweb/consoleql/query.go | 12 +- .../consoleweb/consoleql/query_test.go | 3 +- satellite/console/consoleweb/server.go | 19 + satellite/console/database.go | 2 + satellite/console/resetpasswordtoken.go | 70 ++++ satellite/console/resetpasswordtoken_test.go | 89 +++++ satellite/console/service.go | 54 ++- satellite/satellitedb/consoledb.go | 5 + satellite/satellitedb/dbx/satellitedb.dbx | 23 ++ satellite/satellitedb/dbx/satellitedb.dbx.go | 367 ++++++++++++++++++ .../dbx/satellitedb.dbx.postgres.sql | 7 + .../dbx/satellitedb.dbx.sqlite3.sql | 7 + satellite/satellitedb/locked.go | 41 ++ satellite/satellitedb/migrate.go | 13 + satellite/satellitedb/resetpasstokens.go | 101 +++++ .../satellitedb/testdata/postgres.v19.sql | 242 ++++++++++++ web/satellite/static/emails/Forgot.html | 3 +- .../static/resetPassword/success.css | 66 ++++ .../static/resetPassword/success.html | 34 ++ 20 files changed, 1142 insertions(+), 25 deletions(-) create mode 100644 satellite/console/resetpasswordtoken.go create mode 100644 satellite/console/resetpasswordtoken_test.go create mode 100644 satellite/satellitedb/resetpasstokens.go create mode 100644 satellite/satellitedb/testdata/postgres.v19.sql create mode 100644 web/satellite/static/resetPassword/success.css create mode 100644 web/satellite/static/resetPassword/success.html diff --git a/satellite/console/consoleweb/consoleql/mail.go b/satellite/console/consoleweb/consoleql/mail.go index 67ef0a1ab..fd4d2bc99 100644 --- a/satellite/console/consoleweb/consoleql/mail.go +++ b/satellite/console/consoleweb/consoleql/mail.go @@ -8,6 +8,8 @@ const ( ActivationPath = "activationPath" // PasswordRecoveryPath is key for path which handles password recovery PasswordRecoveryPath = "passwordRecoveryPath" + // CancelPasswordRecoveryPath is key for path which handles let us know sequence + CancelPasswordRecoveryPath = "cancelPasswordRecoveryPath" // SignInPath is key for sign in server route SignInPath = "signInPath" ) @@ -26,9 +28,10 @@ func (*AccountActivationEmail) Subject() string { return "Activate your email" } // ForgotPasswordEmail is mailservice template with reset password data type ForgotPasswordEmail struct { - Origin string - UserName string - ResetLink string + Origin string + UserName string + ResetLink string + CancelPasswordRecoveryLink string } // Template returns email template name diff --git a/satellite/console/consoleweb/consoleql/query.go b/satellite/console/consoleweb/consoleql/query.go index a41a741f9..7a837d8c3 100644 --- a/satellite/console/consoleweb/consoleql/query.go +++ b/satellite/console/consoleweb/consoleql/query.go @@ -112,14 +112,15 @@ func rootQuery(service *console.Service, mailService *mailservice.Service, types return false, fmt.Errorf("%s is not found", email) } - recoveryToken, err := service.GeneratePasswordRecoveryToken(p.Context, user.ID, user.Email) + recoveryToken, err := service.GeneratePasswordRecoveryToken(p.Context, user.ID) if err != nil { return false, errors.New("failed to generate password recovery token") } rootObject := p.Info.RootValue.(map[string]interface{}) origin := rootObject["origin"].(string) - link := origin + rootObject[PasswordRecoveryPath].(string) + recoveryToken + passwordRecoveryLink := origin + rootObject[PasswordRecoveryPath].(string) + recoveryToken + cancelPasswordRecoveryLink := origin + rootObject[CancelPasswordRecoveryPath].(string) + recoveryToken userName := user.ShortName if user.ShortName == "" { userName = user.FullName @@ -131,9 +132,10 @@ func rootQuery(service *console.Service, mailService *mailservice.Service, types p.Context, []post.Address{{Address: user.Email, Name: userName}}, &ForgotPasswordEmail{ - Origin: origin, - ResetLink: link, - UserName: userName, + Origin: origin, + ResetLink: passwordRecoveryLink, + CancelPasswordRecoveryLink: cancelPasswordRecoveryLink, + UserName: userName, }, ) }() diff --git a/satellite/console/consoleweb/consoleql/query_test.go b/satellite/console/consoleweb/consoleql/query_test.go index 9090b6414..d0468cf72 100644 --- a/satellite/console/consoleweb/consoleql/query_test.go +++ b/satellite/console/consoleweb/consoleql/query_test.go @@ -502,7 +502,8 @@ func TestGraphqlQuery(t *testing.T) { }) - rootObject[consoleql.PasswordRecoveryPath] = "?activationToken=" + rootObject[consoleql.PasswordRecoveryPath] = "?token=" + rootObject[consoleql.CancelPasswordRecoveryPath] = "?token=" query := fmt.Sprintf("query {forgotPassword(email: \"%s\")}", user.Email) result := testQuery(t, query) diff --git a/satellite/console/consoleweb/server.go b/satellite/console/consoleweb/server.go index 7211ad1ab..a45a301ea 100644 --- a/satellite/console/consoleweb/server.go +++ b/satellite/console/consoleweb/server.go @@ -94,6 +94,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail if server.config.StaticDir != "" { mux.Handle("/activation/", http.HandlerFunc(server.accountActivationHandler)) mux.Handle("/password-recovery/", http.HandlerFunc(server.passwordRecoveryHandler)) + mux.Handle("/cancel-password-recovery/", http.HandlerFunc(server.cancelPasswordRecoveryHandler)) mux.Handle("/registrationToken/", http.HandlerFunc(server.createRegistrationTokenHandler)) mux.Handle("/usage-report/", http.HandlerFunc(server.bucketUsageReportHandler)) mux.Handle("/static/", http.StripPrefix("/static", fs)) @@ -251,22 +252,27 @@ func (s *Server) passwordRecoveryHandler(w http.ResponseWriter, req *http.Reques err := req.ParseForm() if err != nil { s.serveError(w, req) + s.log.Debug(err.Error()) } password := req.FormValue("password") passwordRepeat := req.FormValue("passwordRepeat") if strings.Compare(password, passwordRepeat) != 0 { s.serveError(w, req) + s.log.Debug(err.Error()) return } err = s.service.ResetPassword(context.Background(), recoveryToken, password) if err != nil { + s.log.Debug(err.Error()) s.serveError(w, req) } + http.ServeFile(w, req, filepath.Join(s.config.StaticDir, "static", "resetPassword", "success.html")) default: t, err := template.ParseFiles(filepath.Join(s.config.StaticDir, "static", "resetPassword", "resetPassword.html")) if err != nil { + s.log.Debug(err.Error()) s.serveError(w, req) } @@ -277,6 +283,18 @@ func (s *Server) passwordRecoveryHandler(w http.ResponseWriter, req *http.Reques } } +func (s *Server) cancelPasswordRecoveryHandler(w http.ResponseWriter, req *http.Request) { + recoveryToken := req.URL.Query().Get("token") + if len(recoveryToken) == 0 { + http.Redirect(w, req, "https://storjlabs.atlassian.net/servicedesk/customer/portals", http.StatusSeeOther) + } + + // No need to check error as we anyway redirect user to support page + _ = s.service.RevokeResetPasswordToken(context.Background(), recoveryToken) + + http.Redirect(w, req, "https://storjlabs.atlassian.net/servicedesk/customer/portals", http.StatusSeeOther) +} + func (s *Server) serveError(w http.ResponseWriter, req *http.Request) { http.ServeFile(w, req, filepath.Join(s.config.StaticDir, "static", "errors", "404.html")) } @@ -305,6 +323,7 @@ func (s *Server) grapqlHandler(w http.ResponseWriter, req *http.Request) { rootObject["origin"] = s.config.ExternalAddress rootObject[consoleql.ActivationPath] = "activation/?token=" rootObject[consoleql.PasswordRecoveryPath] = "password-recovery/?token=" + rootObject[consoleql.CancelPasswordRecoveryPath] = "cancel-password-recovery/?token=" rootObject[consoleql.SignInPath] = "login" result := graphql.Do(graphql.Params{ diff --git a/satellite/console/database.go b/satellite/console/database.go index 8b68e8f4c..3577066c9 100644 --- a/satellite/console/database.go +++ b/satellite/console/database.go @@ -23,6 +23,8 @@ type DB interface { BucketUsage() accounting.BucketUsage // RegistrationTokens is a getter for RegistrationTokens repository RegistrationTokens() RegistrationTokens + // ResetPasswordTokens is a getter for ResetPasswordTokens repository + ResetPasswordTokens() ResetPasswordTokens // UsageRollups is a getter for UsageRollups repository UsageRollups() UsageRollups diff --git a/satellite/console/resetpasswordtoken.go b/satellite/console/resetpasswordtoken.go new file mode 100644 index 000000000..fe1aa8286 --- /dev/null +++ b/satellite/console/resetpasswordtoken.go @@ -0,0 +1,70 @@ +// Copyright (C) 2018 Storj Labs, Inc. +// See LICENSE for copying information. + +package console + +import ( + "context" + "crypto/rand" + "encoding/base64" + "time" + + "github.com/skyrings/skyring-common/tools/uuid" + "github.com/zeebo/errs" +) + +// ResetPasswordTokens is interface for working with reset password tokens +type ResetPasswordTokens interface { + // Create creates new reset password token + Create(ctx context.Context, ownerID uuid.UUID) (*ResetPasswordToken, error) + // GetBySecret retrieves ResetPasswordToken with given secret + GetBySecret(ctx context.Context, secret ResetPasswordSecret) (*ResetPasswordToken, error) + // GetByOwnerID retrieves ResetPasswordToken by ownerID + GetByOwnerID(ctx context.Context, ownerID uuid.UUID) (*ResetPasswordToken, error) + // Delete deletes ResetPasswordToken by ResetPasswordSecret + Delete(ctx context.Context, secret ResetPasswordSecret) error +} + +// ResetPasswordSecret stores secret of registration token +type ResetPasswordSecret [32]byte + +// ResetPasswordToken describing reset password model in the database +type ResetPasswordToken struct { + // Secret is PK of the table and keeps unique value for reset password token + Secret ResetPasswordSecret + // OwnerID stores current token owner ID + OwnerID *uuid.UUID + + CreatedAt time.Time `json:"createdAt"` +} + +// NewResetPasswordSecret creates new reset password secret +func NewResetPasswordSecret() (ResetPasswordSecret, error) { + var b [32]byte + + _, err := rand.Read(b[:]) + if err != nil { + return b, errs.New("error creating registration secret") + } + + return b, nil +} + +// String implements Stringer +func (secret ResetPasswordSecret) String() string { + return base64.URLEncoding.EncodeToString(secret[:]) +} + +// ResetPasswordSecretFromBase64 creates new reset password secret from base64 string +func ResetPasswordSecretFromBase64(s string) (ResetPasswordSecret, error) { + var secret ResetPasswordSecret + + b, err := base64.URLEncoding.DecodeString(s) + if err != nil { + return secret, err + } + + copy(secret[:], b) + + return secret, nil +} diff --git a/satellite/console/resetpasswordtoken_test.go b/satellite/console/resetpasswordtoken_test.go new file mode 100644 index 000000000..74d31b17b --- /dev/null +++ b/satellite/console/resetpasswordtoken_test.go @@ -0,0 +1,89 @@ +// Copyright (C) 2019 Storj Labs, Inc. +// See LICENSE for copying information. + +package console_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "storj.io/storj/internal/testcontext" + "storj.io/storj/satellite" + "storj.io/storj/satellite/console" + "storj.io/storj/satellite/satellitedb/satellitedbtest" +) + +func TestNewRegistrationSecret(t *testing.T) { + // testing constants + const ( + // for user + shortName = "lastName" + email = "email@ukr.net" + pass = "123456" + userFullName = "name" + ) + + satellitedbtest.Run(t, func(t *testing.T, db satellite.DB) { + ctx := testcontext.New(t) + defer ctx.Cleanup() + + users := db.Console().Users() + rptokens := db.Console().ResetPasswordTokens() + + var owner *console.User + var rptoken *console.ResetPasswordToken + + t.Run("Insert reset password token successfully", func(t *testing.T) { + var err error + owner, err = users.Insert(ctx, &console.User{ + FullName: userFullName, + ShortName: shortName, + Email: email, + PasswordHash: []byte(pass), + }) + assert.NoError(t, err) + assert.NotNil(t, owner) + + rptoken, err = rptokens.Create(ctx, owner.ID) + assert.NotNil(t, rptoken) + assert.NoError(t, err) + }) + + t.Run("Get reset password token successfully", func(t *testing.T) { + tokenBySecret, err := rptokens.GetBySecret(ctx, rptoken.Secret) + assert.NoError(t, err) + assert.Equal(t, rptoken.Secret, tokenBySecret.Secret) + assert.Equal(t, rptoken.CreatedAt, tokenBySecret.CreatedAt) + assert.Equal(t, rptoken.OwnerID, tokenBySecret.OwnerID) + }) + + t.Run("Get reset password token by UUID and Secret equal", func(t *testing.T) { + tokenBySecret, err := rptokens.GetBySecret(ctx, rptoken.Secret) + assert.NoError(t, err) + + tokenByUUID, err := rptokens.GetByOwnerID(ctx, *rptoken.OwnerID) + assert.NoError(t, err) + + assert.Equal(t, tokenByUUID.Secret, tokenBySecret.Secret) + assert.Equal(t, tokenByUUID.CreatedAt, tokenBySecret.CreatedAt) + assert.Equal(t, tokenByUUID.OwnerID, tokenBySecret.OwnerID) + }) + + t.Run("Successful base64 encoding", func(t *testing.T) { + base64token := rptoken.Secret.String() + assert.NotEmpty(t, base64token) + }) + + t.Run("Successful base64 decoding", func(t *testing.T) { + base64token := rptoken.Secret.String() + assert.NotEmpty(t, base64token) + + secretFromString, err := console.ResetPasswordSecretFromBase64(base64token) + + assert.NoError(t, err) + assert.Equal(t, rptoken.Secret, secretFromString) + }) + + }) +} diff --git a/satellite/console/service.go b/satellite/console/service.go index 8b7346b52..ef81a2259 100644 --- a/satellite/console/service.go +++ b/satellite/console/service.go @@ -151,16 +151,23 @@ func (s *Service) GenerateActivationToken(ctx context.Context, id uuid.UUID, ema } // GeneratePasswordRecoveryToken - is a method for generating password recovery token -func (s *Service) GeneratePasswordRecoveryToken(ctx context.Context, id uuid.UUID, email string) (token string, err error) { +func (s *Service) GeneratePasswordRecoveryToken(ctx context.Context, id uuid.UUID) (token string, err error) { defer mon.Task()(&ctx)(&err) - claims := &consoleauth.Claims{ - ID: id, - Email: email, - Expiration: time.Now().Add(time.Hour), + resetPasswordToken, err := s.store.ResetPasswordTokens().GetByOwnerID(ctx, id) + if err == nil { + err := s.store.ResetPasswordTokens().Delete(ctx, resetPasswordToken.Secret) + if err != nil { + return "", err + } } - return s.createToken(claims) + resetPasswordToken, err = s.store.ResetPasswordTokens().Create(ctx, id) + if err != nil { + return "", err + } + + return resetPasswordToken.Secret.String(), nil } // ActivateAccount - is a method for activating user account after registration @@ -211,17 +218,16 @@ func (s *Service) ActivateAccount(ctx context.Context, activationToken string) ( func (s *Service) ResetPassword(ctx context.Context, resetPasswordToken, password string) (err error) { defer mon.Task()(&ctx)(&err) - token, err := consoleauth.FromBase64URLString(resetPasswordToken) + secret, err := ResetPasswordSecretFromBase64(resetPasswordToken) + if err != nil { + return + } + token, err := s.store.ResetPasswordTokens().GetBySecret(ctx, secret) if err != nil { return } - claims, err := s.authenticate(token) - if err != nil { - return - } - - user, err := s.store.Users().Get(ctx, claims.ID) + user, err := s.store.Users().Get(ctx, *token.OwnerID) if err != nil { return } @@ -230,7 +236,7 @@ func (s *Service) ResetPassword(ctx context.Context, resetPasswordToken, passwor return err } - if time.Since(claims.Expiration) > 0 { + if time.Since(token.CreatedAt) > tokenExpirationTime { return errs.New(passwordRecoveryTokenIsExpiredErrMsg) } @@ -240,7 +246,25 @@ func (s *Service) ResetPassword(ctx context.Context, resetPasswordToken, passwor } user.PasswordHash = hash - return s.store.Users().Update(ctx, user) + + err = s.store.Users().Update(ctx, user) + if err != nil { + return err + } + + return s.store.ResetPasswordTokens().Delete(ctx, token.Secret) +} + +// RevokeResetPasswordToken - is a method to revoke reset password token +func (s *Service) RevokeResetPasswordToken(ctx context.Context, resetPasswordToken string) (err error) { + defer mon.Task()(&ctx)(&err) + + secret, err := ResetPasswordSecretFromBase64(resetPasswordToken) + if err != nil { + return + } + + return s.store.ResetPasswordTokens().Delete(ctx, secret) } // Token authenticates User by credentials and returns auth token diff --git a/satellite/satellitedb/consoledb.go b/satellite/satellitedb/consoledb.go index 02699b523..c626bd97e 100644 --- a/satellite/satellitedb/consoledb.go +++ b/satellite/satellitedb/consoledb.go @@ -51,6 +51,11 @@ func (db *ConsoleDB) RegistrationTokens() console.RegistrationTokens { return ®istrationTokens{db.methods} } +// ResetPasswordTokens is a getter for ResetPasswordTokens repository +func (db *ConsoleDB) ResetPasswordTokens() console.ResetPasswordTokens { + return &resetPasswordTokens{db.methods} +} + // UsageRollups is a getter for console.UsageRollups repository func (db *ConsoleDB) UsageRollups() console.UsageRollups { return &usagerollups{db.db} diff --git a/satellite/satellitedb/dbx/satellitedb.dbx b/satellite/satellitedb/dbx/satellitedb.dbx index d9ab6c529..63e8b2842 100644 --- a/satellite/satellitedb/dbx/satellitedb.dbx +++ b/satellite/satellitedb/dbx/satellitedb.dbx @@ -519,3 +519,26 @@ read one ( where registration_token.owner_id = ? ) update registration_token ( where registration_token.secret = ? ) + +//--- satellite reset password token ---// + +model reset_password_token ( + key secret + unique owner_id + + field secret blob + field owner_id blob ( updatable ) + + field created_at timestamp ( autoinsert ) +) + +create reset_password_token ( ) +read one ( + select reset_password_token + where reset_password_token.secret = ? +) +read one ( + select reset_password_token + where reset_password_token.owner_id = ? +) +delete reset_password_token ( where reset_password_token.secret = ? ) diff --git a/satellite/satellitedb/dbx/satellitedb.dbx.go b/satellite/satellitedb/dbx/satellitedb.dbx.go index 7eb2aaaa2..639fe7115 100644 --- a/satellite/satellitedb/dbx/satellitedb.dbx.go +++ b/satellite/satellitedb/dbx/satellitedb.dbx.go @@ -400,6 +400,13 @@ CREATE TABLE registration_tokens ( PRIMARY KEY ( secret ), UNIQUE ( owner_id ) ); +CREATE TABLE reset_password_tokens ( + secret bytea NOT NULL, + owner_id bytea NOT NULL, + created_at timestamp with time zone NOT NULL, + PRIMARY KEY ( secret ), + UNIQUE ( owner_id ) +); CREATE TABLE serial_numbers ( id serial NOT NULL, serial_number bytea NOT NULL, @@ -650,6 +657,13 @@ CREATE TABLE registration_tokens ( PRIMARY KEY ( secret ), UNIQUE ( owner_id ) ); +CREATE TABLE reset_password_tokens ( + secret BLOB NOT NULL, + owner_id BLOB NOT NULL, + created_at TIMESTAMP NOT NULL, + PRIMARY KEY ( secret ), + UNIQUE ( owner_id ) +); CREATE TABLE serial_numbers ( id INTEGER NOT NULL, serial_number BLOB NOT NULL, @@ -2757,6 +2771,75 @@ func (f RegistrationToken_CreatedAt_Field) value() interface{} { func (RegistrationToken_CreatedAt_Field) _Column() string { return "created_at" } +type ResetPasswordToken struct { + Secret []byte + OwnerId []byte + CreatedAt time.Time +} + +func (ResetPasswordToken) _Table() string { return "reset_password_tokens" } + +type ResetPasswordToken_Update_Fields struct { + OwnerId ResetPasswordToken_OwnerId_Field +} + +type ResetPasswordToken_Secret_Field struct { + _set bool + _null bool + _value []byte +} + +func ResetPasswordToken_Secret(v []byte) ResetPasswordToken_Secret_Field { + return ResetPasswordToken_Secret_Field{_set: true, _value: v} +} + +func (f ResetPasswordToken_Secret_Field) value() interface{} { + if !f._set || f._null { + return nil + } + return f._value +} + +func (ResetPasswordToken_Secret_Field) _Column() string { return "secret" } + +type ResetPasswordToken_OwnerId_Field struct { + _set bool + _null bool + _value []byte +} + +func ResetPasswordToken_OwnerId(v []byte) ResetPasswordToken_OwnerId_Field { + return ResetPasswordToken_OwnerId_Field{_set: true, _value: v} +} + +func (f ResetPasswordToken_OwnerId_Field) value() interface{} { + if !f._set || f._null { + return nil + } + return f._value +} + +func (ResetPasswordToken_OwnerId_Field) _Column() string { return "owner_id" } + +type ResetPasswordToken_CreatedAt_Field struct { + _set bool + _null bool + _value time.Time +} + +func ResetPasswordToken_CreatedAt(v time.Time) ResetPasswordToken_CreatedAt_Field { + return ResetPasswordToken_CreatedAt_Field{_set: true, _value: v} +} + +func (f ResetPasswordToken_CreatedAt_Field) value() interface{} { + if !f._set || f._null { + return nil + } + return f._value +} + +func (ResetPasswordToken_CreatedAt_Field) _Column() string { return "created_at" } + type SerialNumber struct { Id int SerialNumber []byte @@ -4132,6 +4215,30 @@ func (obj *postgresImpl) Create_RegistrationToken(ctx context.Context, } +func (obj *postgresImpl) Create_ResetPasswordToken(ctx context.Context, + reset_password_token_secret ResetPasswordToken_Secret_Field, + reset_password_token_owner_id ResetPasswordToken_OwnerId_Field) ( + reset_password_token *ResetPasswordToken, err error) { + + __now := obj.db.Hooks.Now().UTC() + __secret_val := reset_password_token_secret.value() + __owner_id_val := reset_password_token_owner_id.value() + __created_at_val := __now + + var __embed_stmt = __sqlbundle_Literal("INSERT INTO reset_password_tokens ( secret, owner_id, created_at ) VALUES ( ?, ?, ? ) RETURNING reset_password_tokens.secret, reset_password_tokens.owner_id, reset_password_tokens.created_at") + + var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt) + obj.logStmt(__stmt, __secret_val, __owner_id_val, __created_at_val) + + reset_password_token = &ResetPasswordToken{} + err = obj.driver.QueryRow(__stmt, __secret_val, __owner_id_val, __created_at_val).Scan(&reset_password_token.Secret, &reset_password_token.OwnerId, &reset_password_token.CreatedAt) + if err != nil { + return nil, obj.makeErr(err) + } + return reset_password_token, nil + +} + func (obj *postgresImpl) Get_Irreparabledb_By_Segmentpath(ctx context.Context, irreparabledb_segmentpath Irreparabledb_Segmentpath_Field) ( irreparabledb *Irreparabledb, err error) { @@ -5102,6 +5209,48 @@ func (obj *postgresImpl) Get_RegistrationToken_By_OwnerId(ctx context.Context, } +func (obj *postgresImpl) Get_ResetPasswordToken_By_Secret(ctx context.Context, + reset_password_token_secret ResetPasswordToken_Secret_Field) ( + reset_password_token *ResetPasswordToken, err error) { + + var __embed_stmt = __sqlbundle_Literal("SELECT reset_password_tokens.secret, reset_password_tokens.owner_id, reset_password_tokens.created_at FROM reset_password_tokens WHERE reset_password_tokens.secret = ?") + + var __values []interface{} + __values = append(__values, reset_password_token_secret.value()) + + var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt) + obj.logStmt(__stmt, __values...) + + reset_password_token = &ResetPasswordToken{} + err = obj.driver.QueryRow(__stmt, __values...).Scan(&reset_password_token.Secret, &reset_password_token.OwnerId, &reset_password_token.CreatedAt) + if err != nil { + return nil, obj.makeErr(err) + } + return reset_password_token, nil + +} + +func (obj *postgresImpl) Get_ResetPasswordToken_By_OwnerId(ctx context.Context, + reset_password_token_owner_id ResetPasswordToken_OwnerId_Field) ( + reset_password_token *ResetPasswordToken, err error) { + + var __embed_stmt = __sqlbundle_Literal("SELECT reset_password_tokens.secret, reset_password_tokens.owner_id, reset_password_tokens.created_at FROM reset_password_tokens WHERE reset_password_tokens.owner_id = ?") + + var __values []interface{} + __values = append(__values, reset_password_token_owner_id.value()) + + var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt) + obj.logStmt(__stmt, __values...) + + reset_password_token = &ResetPasswordToken{} + err = obj.driver.QueryRow(__stmt, __values...).Scan(&reset_password_token.Secret, &reset_password_token.OwnerId, &reset_password_token.CreatedAt) + if err != nil { + return nil, obj.makeErr(err) + } + return reset_password_token, nil + +} + func (obj *postgresImpl) Update_Irreparabledb_By_Segmentpath(ctx context.Context, irreparabledb_segmentpath Irreparabledb_Segmentpath_Field, update Irreparabledb_Update_Fields) ( @@ -5846,6 +5995,32 @@ func (obj *postgresImpl) Delete_CertRecord_By_Id(ctx context.Context, } +func (obj *postgresImpl) Delete_ResetPasswordToken_By_Secret(ctx context.Context, + reset_password_token_secret ResetPasswordToken_Secret_Field) ( + deleted bool, err error) { + + var __embed_stmt = __sqlbundle_Literal("DELETE FROM reset_password_tokens WHERE reset_password_tokens.secret = ?") + + var __values []interface{} + __values = append(__values, reset_password_token_secret.value()) + + var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt) + obj.logStmt(__stmt, __values...) + + __res, err := obj.driver.Exec(__stmt, __values...) + if err != nil { + return false, obj.makeErr(err) + } + + __count, err := __res.RowsAffected() + if err != nil { + return false, obj.makeErr(err) + } + + return __count > 0, nil + +} + func (impl postgresImpl) isConstraintError(err error) ( constraint string, ok bool) { if e, ok := err.(*pq.Error); ok { @@ -5924,6 +6099,16 @@ func (obj *postgresImpl) deleteAll(ctx context.Context) (count int64, err error) return 0, obj.makeErr(err) } + __count, err = __res.RowsAffected() + if err != nil { + return 0, obj.makeErr(err) + } + count += __count + __res, err = obj.driver.Exec("DELETE FROM reset_password_tokens;") + if err != nil { + return 0, obj.makeErr(err) + } + __count, err = __res.RowsAffected() if err != nil { return 0, obj.makeErr(err) @@ -6549,6 +6734,33 @@ func (obj *sqlite3Impl) Create_RegistrationToken(ctx context.Context, } +func (obj *sqlite3Impl) Create_ResetPasswordToken(ctx context.Context, + reset_password_token_secret ResetPasswordToken_Secret_Field, + reset_password_token_owner_id ResetPasswordToken_OwnerId_Field) ( + reset_password_token *ResetPasswordToken, err error) { + + __now := obj.db.Hooks.Now().UTC() + __secret_val := reset_password_token_secret.value() + __owner_id_val := reset_password_token_owner_id.value() + __created_at_val := __now + + var __embed_stmt = __sqlbundle_Literal("INSERT INTO reset_password_tokens ( secret, owner_id, created_at ) VALUES ( ?, ?, ? )") + + var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt) + obj.logStmt(__stmt, __secret_val, __owner_id_val, __created_at_val) + + __res, err := obj.driver.Exec(__stmt, __secret_val, __owner_id_val, __created_at_val) + if err != nil { + return nil, obj.makeErr(err) + } + __pk, err := __res.LastInsertId() + if err != nil { + return nil, obj.makeErr(err) + } + return obj.getLastResetPasswordToken(ctx, __pk) + +} + func (obj *sqlite3Impl) Get_Irreparabledb_By_Segmentpath(ctx context.Context, irreparabledb_segmentpath Irreparabledb_Segmentpath_Field) ( irreparabledb *Irreparabledb, err error) { @@ -7519,6 +7731,48 @@ func (obj *sqlite3Impl) Get_RegistrationToken_By_OwnerId(ctx context.Context, } +func (obj *sqlite3Impl) Get_ResetPasswordToken_By_Secret(ctx context.Context, + reset_password_token_secret ResetPasswordToken_Secret_Field) ( + reset_password_token *ResetPasswordToken, err error) { + + var __embed_stmt = __sqlbundle_Literal("SELECT reset_password_tokens.secret, reset_password_tokens.owner_id, reset_password_tokens.created_at FROM reset_password_tokens WHERE reset_password_tokens.secret = ?") + + var __values []interface{} + __values = append(__values, reset_password_token_secret.value()) + + var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt) + obj.logStmt(__stmt, __values...) + + reset_password_token = &ResetPasswordToken{} + err = obj.driver.QueryRow(__stmt, __values...).Scan(&reset_password_token.Secret, &reset_password_token.OwnerId, &reset_password_token.CreatedAt) + if err != nil { + return nil, obj.makeErr(err) + } + return reset_password_token, nil + +} + +func (obj *sqlite3Impl) Get_ResetPasswordToken_By_OwnerId(ctx context.Context, + reset_password_token_owner_id ResetPasswordToken_OwnerId_Field) ( + reset_password_token *ResetPasswordToken, err error) { + + var __embed_stmt = __sqlbundle_Literal("SELECT reset_password_tokens.secret, reset_password_tokens.owner_id, reset_password_tokens.created_at FROM reset_password_tokens WHERE reset_password_tokens.owner_id = ?") + + var __values []interface{} + __values = append(__values, reset_password_token_owner_id.value()) + + var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt) + obj.logStmt(__stmt, __values...) + + reset_password_token = &ResetPasswordToken{} + err = obj.driver.QueryRow(__stmt, __values...).Scan(&reset_password_token.Secret, &reset_password_token.OwnerId, &reset_password_token.CreatedAt) + if err != nil { + return nil, obj.makeErr(err) + } + return reset_password_token, nil + +} + func (obj *sqlite3Impl) Update_Irreparabledb_By_Segmentpath(ctx context.Context, irreparabledb_segmentpath Irreparabledb_Segmentpath_Field, update Irreparabledb_Update_Fields) ( @@ -8343,6 +8597,32 @@ func (obj *sqlite3Impl) Delete_CertRecord_By_Id(ctx context.Context, } +func (obj *sqlite3Impl) Delete_ResetPasswordToken_By_Secret(ctx context.Context, + reset_password_token_secret ResetPasswordToken_Secret_Field) ( + deleted bool, err error) { + + var __embed_stmt = __sqlbundle_Literal("DELETE FROM reset_password_tokens WHERE reset_password_tokens.secret = ?") + + var __values []interface{} + __values = append(__values, reset_password_token_secret.value()) + + var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt) + obj.logStmt(__stmt, __values...) + + __res, err := obj.driver.Exec(__stmt, __values...) + if err != nil { + return false, obj.makeErr(err) + } + + __count, err := __res.RowsAffected() + if err != nil { + return false, obj.makeErr(err) + } + + return __count > 0, nil + +} + func (obj *sqlite3Impl) getLastIrreparabledb(ctx context.Context, pk int64) ( irreparabledb *Irreparabledb, err error) { @@ -8613,6 +8893,24 @@ func (obj *sqlite3Impl) getLastRegistrationToken(ctx context.Context, } +func (obj *sqlite3Impl) getLastResetPasswordToken(ctx context.Context, + pk int64) ( + reset_password_token *ResetPasswordToken, err error) { + + var __embed_stmt = __sqlbundle_Literal("SELECT reset_password_tokens.secret, reset_password_tokens.owner_id, reset_password_tokens.created_at FROM reset_password_tokens WHERE _rowid_ = ?") + + var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt) + obj.logStmt(__stmt, pk) + + reset_password_token = &ResetPasswordToken{} + err = obj.driver.QueryRow(__stmt, pk).Scan(&reset_password_token.Secret, &reset_password_token.OwnerId, &reset_password_token.CreatedAt) + if err != nil { + return nil, obj.makeErr(err) + } + return reset_password_token, nil + +} + func (impl sqlite3Impl) isConstraintError(err error) ( constraint string, ok bool) { if e, ok := err.(sqlite3.Error); ok { @@ -8696,6 +8994,16 @@ func (obj *sqlite3Impl) deleteAll(ctx context.Context) (count int64, err error) return 0, obj.makeErr(err) } + __count, err = __res.RowsAffected() + if err != nil { + return 0, obj.makeErr(err) + } + count += __count + __res, err = obj.driver.Exec("DELETE FROM reset_password_tokens;") + if err != nil { + return 0, obj.makeErr(err) + } + __count, err = __res.RowsAffected() if err != nil { return 0, obj.makeErr(err) @@ -9151,6 +9459,18 @@ func (rx *Rx) Create_RegistrationToken(ctx context.Context, } +func (rx *Rx) Create_ResetPasswordToken(ctx context.Context, + reset_password_token_secret ResetPasswordToken_Secret_Field, + reset_password_token_owner_id ResetPasswordToken_OwnerId_Field) ( + reset_password_token *ResetPasswordToken, err error) { + var tx *Tx + if tx, err = rx.getTx(ctx); err != nil { + return + } + return tx.Create_ResetPasswordToken(ctx, reset_password_token_secret, reset_password_token_owner_id) + +} + func (rx *Rx) Create_SerialNumber(ctx context.Context, serial_number_serial_number SerialNumber_SerialNumber_Field, serial_number_bucket_id SerialNumber_BucketId_Field, @@ -9285,6 +9605,16 @@ func (rx *Rx) Delete_Project_By_Id(ctx context.Context, return tx.Delete_Project_By_Id(ctx, project_id) } +func (rx *Rx) Delete_ResetPasswordToken_By_Secret(ctx context.Context, + reset_password_token_secret ResetPasswordToken_Secret_Field) ( + deleted bool, err error) { + var tx *Tx + if tx, err = rx.getTx(ctx); err != nil { + return + } + return tx.Delete_ResetPasswordToken_By_Secret(ctx, reset_password_token_secret) +} + func (rx *Rx) Delete_SerialNumber_By_ExpiresAt_LessOrEqual(ctx context.Context, serial_number_expires_at_less_or_equal SerialNumber_ExpiresAt_Field) ( count int64, err error) { @@ -9471,6 +9801,26 @@ func (rx *Rx) Get_RegistrationToken_By_Secret(ctx context.Context, return tx.Get_RegistrationToken_By_Secret(ctx, registration_token_secret) } +func (rx *Rx) Get_ResetPasswordToken_By_OwnerId(ctx context.Context, + reset_password_token_owner_id ResetPasswordToken_OwnerId_Field) ( + reset_password_token *ResetPasswordToken, err error) { + var tx *Tx + if tx, err = rx.getTx(ctx); err != nil { + return + } + return tx.Get_ResetPasswordToken_By_OwnerId(ctx, reset_password_token_owner_id) +} + +func (rx *Rx) Get_ResetPasswordToken_By_Secret(ctx context.Context, + reset_password_token_secret ResetPasswordToken_Secret_Field) ( + reset_password_token *ResetPasswordToken, err error) { + var tx *Tx + if tx, err = rx.getTx(ctx); err != nil { + return + } + return tx.Get_ResetPasswordToken_By_Secret(ctx, reset_password_token_secret) +} + func (rx *Rx) Get_StoragenodeStorageTally_By_Id(ctx context.Context, storagenode_storage_tally_id StoragenodeStorageTally_Id_Field) ( storagenode_storage_tally *StoragenodeStorageTally, err error) { @@ -9794,6 +10144,11 @@ type Methods interface { optional RegistrationToken_Create_Fields) ( registration_token *RegistrationToken, err error) + Create_ResetPasswordToken(ctx context.Context, + reset_password_token_secret ResetPasswordToken_Secret_Field, + reset_password_token_owner_id ResetPasswordToken_OwnerId_Field) ( + reset_password_token *ResetPasswordToken, err error) + Create_SerialNumber(ctx context.Context, serial_number_serial_number SerialNumber_SerialNumber_Field, serial_number_bucket_id SerialNumber_BucketId_Field, @@ -9852,6 +10207,10 @@ type Methods interface { project_id Project_Id_Field) ( deleted bool, err error) + Delete_ResetPasswordToken_By_Secret(ctx context.Context, + reset_password_token_secret ResetPasswordToken_Secret_Field) ( + deleted bool, err error) + Delete_SerialNumber_By_ExpiresAt_LessOrEqual(ctx context.Context, serial_number_expires_at_less_or_equal SerialNumber_ExpiresAt_Field) ( count int64, err error) @@ -9929,6 +10288,14 @@ type Methods interface { registration_token_secret RegistrationToken_Secret_Field) ( registration_token *RegistrationToken, err error) + Get_ResetPasswordToken_By_OwnerId(ctx context.Context, + reset_password_token_owner_id ResetPasswordToken_OwnerId_Field) ( + reset_password_token *ResetPasswordToken, err error) + + Get_ResetPasswordToken_By_Secret(ctx context.Context, + reset_password_token_secret ResetPasswordToken_Secret_Field) ( + reset_password_token *ResetPasswordToken, err error) + Get_StoragenodeStorageTally_By_Id(ctx context.Context, storagenode_storage_tally_id StoragenodeStorageTally_Id_Field) ( storagenode_storage_tally *StoragenodeStorageTally, err error) diff --git a/satellite/satellitedb/dbx/satellitedb.dbx.postgres.sql b/satellite/satellitedb/dbx/satellitedb.dbx.postgres.sql index 1a78f0b38..e3861d384 100644 --- a/satellite/satellitedb/dbx/satellitedb.dbx.postgres.sql +++ b/satellite/satellitedb/dbx/satellitedb.dbx.postgres.sql @@ -128,6 +128,13 @@ CREATE TABLE registration_tokens ( PRIMARY KEY ( secret ), UNIQUE ( owner_id ) ); +CREATE TABLE reset_password_tokens ( + secret bytea NOT NULL, + owner_id bytea NOT NULL, + created_at timestamp with time zone NOT NULL, + PRIMARY KEY ( secret ), + UNIQUE ( owner_id ) +); CREATE TABLE serial_numbers ( id serial NOT NULL, serial_number bytea NOT NULL, diff --git a/satellite/satellitedb/dbx/satellitedb.dbx.sqlite3.sql b/satellite/satellitedb/dbx/satellitedb.dbx.sqlite3.sql index d036f49bb..de7e9c204 100644 --- a/satellite/satellitedb/dbx/satellitedb.dbx.sqlite3.sql +++ b/satellite/satellitedb/dbx/satellitedb.dbx.sqlite3.sql @@ -128,6 +128,13 @@ CREATE TABLE registration_tokens ( PRIMARY KEY ( secret ), UNIQUE ( owner_id ) ); +CREATE TABLE reset_password_tokens ( + secret BLOB NOT NULL, + owner_id BLOB NOT NULL, + created_at TIMESTAMP NOT NULL, + PRIMARY KEY ( secret ), + UNIQUE ( owner_id ) +); CREATE TABLE serial_numbers ( id INTEGER NOT NULL, serial_number BLOB NOT NULL, diff --git a/satellite/satellitedb/locked.go b/satellite/satellitedb/locked.go index be5052ba1..f19f179a8 100644 --- a/satellite/satellitedb/locked.go +++ b/satellite/satellitedb/locked.go @@ -361,6 +361,47 @@ func (m *lockedRegistrationTokens) UpdateOwner(ctx context.Context, secret conso return m.db.UpdateOwner(ctx, secret, ownerID) } +// ResetPasswordTokens is a getter for ResetPasswordTokens repository +func (m *lockedConsole) ResetPasswordTokens() console.ResetPasswordTokens { + m.Lock() + defer m.Unlock() + return &lockedResetPasswordTokens{m.Locker, m.db.ResetPasswordTokens()} +} + +// lockedResetPasswordTokens implements locking wrapper for console.ResetPasswordTokens +type lockedResetPasswordTokens struct { + sync.Locker + db console.ResetPasswordTokens +} + +// Create creates new reset password token +func (m *lockedResetPasswordTokens) Create(ctx context.Context, ownerID uuid.UUID) (*console.ResetPasswordToken, error) { + m.Lock() + defer m.Unlock() + return m.db.Create(ctx, ownerID) +} + +// Delete deletes ResetPasswordToken by ResetPasswordSecret +func (m *lockedResetPasswordTokens) Delete(ctx context.Context, secret console.ResetPasswordSecret) error { + m.Lock() + defer m.Unlock() + return m.db.Delete(ctx, secret) +} + +// GetByOwnerID retrieves ResetPasswordToken by ownerID +func (m *lockedResetPasswordTokens) GetByOwnerID(ctx context.Context, ownerID uuid.UUID) (*console.ResetPasswordToken, error) { + m.Lock() + defer m.Unlock() + return m.db.GetByOwnerID(ctx, ownerID) +} + +// GetBySecret retrieves ResetPasswordToken with given secret +func (m *lockedResetPasswordTokens) GetBySecret(ctx context.Context, secret console.ResetPasswordSecret) (*console.ResetPasswordToken, error) { + m.Lock() + defer m.Unlock() + return m.db.GetBySecret(ctx, secret) +} + // UsageRollups is a getter for UsageRollups repository func (m *lockedConsole) UsageRollups() console.UsageRollups { m.Lock() diff --git a/satellite/satellitedb/migrate.go b/satellite/satellitedb/migrate.go index 688a33ae4..2b917a6de 100644 --- a/satellite/satellitedb/migrate.go +++ b/satellite/satellitedb/migrate.go @@ -620,6 +620,19 @@ func (db *DB) PostgresMigration() *migrate.Migration { `ALTER TABLE storagenode_storage_tallies DROP COLUMN created_at`, }, }, + { + Description: "Added new table to store reset password tokens", + Version: 19, + Action: migrate.SQL{` + CREATE TABLE reset_password_tokens ( + secret bytea NOT NULL, + owner_id bytea NOT NULL, + created_at timestamp with time zone NOT NULL, + PRIMARY KEY ( secret ), + UNIQUE ( owner_id ) + );`, + }, + }, }, } } diff --git a/satellite/satellitedb/resetpasstokens.go b/satellite/satellitedb/resetpasstokens.go new file mode 100644 index 000000000..442e84729 --- /dev/null +++ b/satellite/satellitedb/resetpasstokens.go @@ -0,0 +1,101 @@ +// Copyright (C) 2018 Storj Labs, Inc. +// See LICENSE for copying information. + +package satellitedb + +import ( + "context" + "errors" + + "github.com/skyrings/skyring-common/tools/uuid" + + "storj.io/storj/satellite/console" + dbx "storj.io/storj/satellite/satellitedb/dbx" +) + +type resetPasswordTokens struct { + db dbx.Methods +} + +// Create creates new reset password token +func (rpt *resetPasswordTokens) Create(ctx context.Context, ownerID uuid.UUID) (*console.ResetPasswordToken, error) { + secret, err := console.NewResetPasswordSecret() + if err != nil { + return nil, err + } + + resToken, err := rpt.db.Create_ResetPasswordToken( + ctx, + dbx.ResetPasswordToken_Secret(secret[:]), + dbx.ResetPasswordToken_OwnerId(ownerID[:]), + ) + if err != nil { + return nil, err + } + + return resetPasswordTokenFromDBX(resToken) +} + +// GetBySecret retrieves ResetPasswordToken with given Secret +func (rpt *resetPasswordTokens) GetBySecret(ctx context.Context, secret console.ResetPasswordSecret) (*console.ResetPasswordToken, error) { + resToken, err := rpt.db.Get_ResetPasswordToken_By_Secret( + ctx, + dbx.ResetPasswordToken_Secret(secret[:]), + ) + if err != nil { + return nil, err + } + + return resetPasswordTokenFromDBX(resToken) +} + +// GetByOwnerID retrieves ResetPasswordToken by ownerID +func (rpt *resetPasswordTokens) GetByOwnerID(ctx context.Context, ownerID uuid.UUID) (*console.ResetPasswordToken, error) { + resToken, err := rpt.db.Get_ResetPasswordToken_By_OwnerId( + ctx, + dbx.ResetPasswordToken_OwnerId(ownerID[:]), + ) + if err != nil { + return nil, err + } + + return resetPasswordTokenFromDBX(resToken) +} + +// Delete deletes ResetPasswordToken by ResetPasswordSecret +func (rpt *resetPasswordTokens) Delete(ctx context.Context, secret console.ResetPasswordSecret) error { + _, err := rpt.db.Delete_ResetPasswordToken_By_Secret( + ctx, + dbx.ResetPasswordToken_Secret(secret[:]), + ) + + return err +} + +// resetPasswordTokenFromDBX is used for creating ResetPasswordToken entity from autogenerated dbx.ResetPasswordToken struct +func resetPasswordTokenFromDBX(resetToken *dbx.ResetPasswordToken) (*console.ResetPasswordToken, error) { + if resetToken == nil { + return nil, errors.New("token parameter is nil") + } + + var secret [32]byte + + copy(secret[:], resetToken.Secret) + + result := &console.ResetPasswordToken{ + Secret: secret, + OwnerID: nil, + CreatedAt: resetToken.CreatedAt, + } + + if resetToken.OwnerId != nil { + ownerID, err := bytesToUUID(resetToken.OwnerId) + if err != nil { + return nil, err + } + + result.OwnerID = &ownerID + } + + return result, nil +} diff --git a/satellite/satellitedb/testdata/postgres.v19.sql b/satellite/satellitedb/testdata/postgres.v19.sql new file mode 100644 index 000000000..563e9a732 --- /dev/null +++ b/satellite/satellitedb/testdata/postgres.v19.sql @@ -0,0 +1,242 @@ +-- Copied from the corresponding version of dbx generated schema +CREATE TABLE accounting_rollups ( + id bigserial NOT NULL, + node_id bytea NOT NULL, + start_time timestamp with time zone NOT NULL, + put_total bigint NOT NULL, + get_total bigint NOT NULL, + get_audit_total bigint NOT NULL, + get_repair_total bigint NOT NULL, + put_repair_total bigint NOT NULL, + at_rest_total double precision NOT NULL, + PRIMARY KEY ( id ) +); +CREATE TABLE accounting_timestamps ( + name text NOT NULL, + value timestamp with time zone NOT NULL, + PRIMARY KEY ( name ) +); +CREATE TABLE bucket_bandwidth_rollups ( + bucket_name bytea NOT NULL, + project_id bytea NOT NULL, + interval_start timestamp NOT NULL, + interval_seconds integer NOT NULL, + action integer NOT NULL, + inline bigint NOT NULL, + allocated bigint NOT NULL, + settled bigint NOT NULL, + PRIMARY KEY ( bucket_name, project_id, interval_start, action ) +); +CREATE TABLE bucket_storage_tallies ( + bucket_name bytea NOT NULL, + project_id bytea NOT NULL, + interval_start timestamp NOT NULL, + inline bigint NOT NULL, + remote bigint NOT NULL, + remote_segments_count integer NOT NULL, + inline_segments_count integer NOT NULL, + object_count integer NOT NULL, + metadata_size bigint NOT NULL, + PRIMARY KEY ( bucket_name, project_id, interval_start ) +); +CREATE TABLE bucket_usages ( + id bytea NOT NULL, + bucket_id bytea NOT NULL, + rollup_end_time timestamp with time zone NOT NULL, + remote_stored_data bigint NOT NULL, + inline_stored_data bigint NOT NULL, + remote_segments integer NOT NULL, + inline_segments integer NOT NULL, + objects integer NOT NULL, + metadata_size bigint NOT NULL, + repair_egress bigint NOT NULL, + get_egress bigint NOT NULL, + audit_egress bigint NOT NULL, + PRIMARY KEY ( id ) +); +CREATE TABLE bwagreements ( + serialnum text NOT NULL, + storage_node_id bytea NOT NULL, + uplink_id bytea NOT NULL, + action bigint NOT NULL, + total bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + expires_at timestamp with time zone NOT NULL, + PRIMARY KEY ( serialnum ) +); +CREATE TABLE certRecords ( + publickey bytea NOT NULL, + id bytea NOT NULL, + update_at timestamp with time zone NOT NULL, + PRIMARY KEY ( id ) +); +CREATE TABLE injuredsegments ( + path text NOT NULL, + data bytea NOT NULL, + attempted timestamp, + PRIMARY KEY ( path ) +); +CREATE TABLE irreparabledbs ( + segmentpath bytea NOT NULL, + segmentdetail bytea NOT NULL, + pieces_lost_count bigint NOT NULL, + seg_damaged_unix_sec bigint NOT NULL, + repair_attempt_count bigint NOT NULL, + PRIMARY KEY ( segmentpath ) +); +CREATE TABLE nodes ( + id bytea NOT NULL, + address text NOT NULL, + protocol integer NOT NULL, + type integer NOT NULL, + email text NOT NULL, + wallet text NOT NULL, + free_bandwidth bigint NOT NULL, + free_disk bigint NOT NULL, + major bigint NOT NULL, + minor bigint NOT NULL, + patch bigint NOT NULL, + hash text NOT NULL, + timestamp timestamp with time zone NOT NULL, + release boolean NOT NULL, + latency_90 bigint NOT NULL, + audit_success_count bigint NOT NULL, + total_audit_count bigint NOT NULL, + audit_success_ratio double precision NOT NULL, + uptime_success_count bigint NOT NULL, + total_uptime_count bigint NOT NULL, + uptime_ratio double precision NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + last_contact_success timestamp with time zone NOT NULL, + last_contact_failure timestamp with time zone NOT NULL, + PRIMARY KEY ( id ) +); +CREATE TABLE projects ( + id bytea NOT NULL, + name text NOT NULL, + description text NOT NULL, + created_at timestamp with time zone NOT NULL, + PRIMARY KEY ( id ) +); +CREATE TABLE registration_tokens ( + secret bytea NOT NULL, + owner_id bytea, + project_limit integer NOT NULL, + created_at timestamp with time zone NOT NULL, + PRIMARY KEY ( secret ), + UNIQUE ( owner_id ) +); +CREATE TABLE serial_numbers ( + id serial NOT NULL, + serial_number bytea NOT NULL, + bucket_id bytea NOT NULL, + expires_at timestamp NOT NULL, + PRIMARY KEY ( id ) +); +CREATE TABLE storagenode_bandwidth_rollups ( + storagenode_id bytea NOT NULL, + interval_start timestamp NOT NULL, + interval_seconds integer NOT NULL, + action integer NOT NULL, + allocated bigint NOT NULL, + settled bigint NOT NULL, + PRIMARY KEY ( storagenode_id, interval_start, action ) +); +CREATE TABLE storagenode_storage_tallies ( + id bigserial NOT NULL, + node_id bytea NOT NULL, + interval_end_time timestamp with time zone NOT NULL, + data_total double precision NOT NULL, + PRIMARY KEY ( id ) +); +CREATE TABLE users ( + id bytea NOT NULL, + full_name text NOT NULL, + short_name text, + email text NOT NULL, + password_hash bytea NOT NULL, + status integer NOT NULL, + created_at timestamp with time zone NOT NULL, + PRIMARY KEY ( id ) +); +CREATE TABLE api_keys ( + id bytea NOT NULL, + project_id bytea NOT NULL REFERENCES projects( id ) ON DELETE CASCADE, + key bytea NOT NULL, + name text NOT NULL, + created_at timestamp with time zone NOT NULL, + PRIMARY KEY ( id ), + UNIQUE ( key ), + UNIQUE ( name, project_id ) +); +CREATE TABLE project_members ( + member_id bytea NOT NULL REFERENCES users( id ) ON DELETE CASCADE, + project_id bytea NOT NULL REFERENCES projects( id ) ON DELETE CASCADE, + created_at timestamp with time zone NOT NULL, + PRIMARY KEY ( member_id, project_id ) +); +CREATE TABLE used_serials ( + serial_number_id integer NOT NULL REFERENCES serial_numbers( id ) ON DELETE CASCADE, + storage_node_id bytea NOT NULL, + PRIMARY KEY ( serial_number_id, storage_node_id ) +); +CREATE TABLE reset_password_tokens ( + secret bytea NOT NULL, + owner_id bytea NOT NULL, + created_at timestamp with time zone NOT NULL, + PRIMARY KEY ( secret ), + UNIQUE ( owner_id ) +); +CREATE INDEX bucket_name_project_id_interval_start_interval_seconds ON bucket_bandwidth_rollups ( bucket_name, project_id, interval_start, interval_seconds ); +CREATE UNIQUE INDEX bucket_id_rollup ON bucket_usages ( bucket_id, rollup_end_time ); +CREATE UNIQUE INDEX serial_number ON serial_numbers ( serial_number ); +CREATE INDEX serial_numbers_expires_at_index ON serial_numbers ( expires_at ); +CREATE INDEX storagenode_id_interval_start_interval_seconds ON storagenode_bandwidth_rollups ( storagenode_id, interval_start, interval_seconds ); + +--- + +INSERT INTO "accounting_rollups"("id", "node_id", "start_time", "put_total", "get_total", "get_audit_total", "get_repair_total", "put_repair_total", "at_rest_total") VALUES (1, E'\\367M\\177\\251]t/\\022\\256\\214\\265\\025\\224\\204:\\217\\212\\0102<\\321\\374\\020&\\271Qc\\325\\261\\354\\246\\233'::bytea, '2019-02-09 00:00:00+00', 1000, 2000, 3000, 4000, 0, 5000); + +INSERT INTO "accounting_timestamps" VALUES ('LastAtRestTally', '0001-01-01 00:00:00+00'); +INSERT INTO "accounting_timestamps" VALUES ('LastRollup', '0001-01-01 00:00:00+00'); +INSERT INTO "accounting_timestamps" VALUES ('LastBandwidthTally', '0001-01-01 00:00:00+00'); + +INSERT INTO "nodes"("id", "address", "protocol", "type", "email", "wallet", "free_bandwidth", "free_disk", "major", "minor", "patch", "hash", "timestamp", "release","latency_90", "audit_success_count", "total_audit_count", "audit_success_ratio", "uptime_success_count", "total_uptime_count", "uptime_ratio", "created_at", "updated_at", "last_contact_success", "last_contact_failure") VALUES (E'\\153\\313\\233\\074\\327\\177\\136\\070\\346\\001', '127.0.0.1:55516', 0, 4, '', '', -1, -1, 0, 1, 0, '', 'epoch', false, 0, 0, 5, 0, 0, 5, 0, '2019-02-14 08:07:31.028103+00', '2019-02-14 08:07:31.108963+00', 'epoch', 'epoch'); +INSERT INTO "nodes"("id", "address", "protocol", "type", "email", "wallet", "free_bandwidth", "free_disk", "major", "minor", "patch", "hash", "timestamp", "release","latency_90", "audit_success_count", "total_audit_count", "audit_success_ratio", "uptime_success_count", "total_uptime_count", "uptime_ratio", "created_at", "updated_at", "last_contact_success", "last_contact_failure") VALUES (E'\\006\\223\\250R\\221\\005\\365\\377v>0\\266\\365\\216\\255?\\347\\244\\371?2\\264\\262\\230\\007<\\001\\262\\263\\237\\247n', '127.0.0.1:55518', 0, 4, '', '', -1, -1, 0, 1, 0, '', 'epoch', false, 0, 0, 0, 1, 3, 3, 1, '2019-02-14 08:07:31.028103+00', '2019-02-14 08:07:31.108963+00', 'epoch', 'epoch'); +INSERT INTO "nodes"("id", "address", "protocol", "type", "email", "wallet", "free_bandwidth", "free_disk", "major", "minor", "patch", "hash", "timestamp", "release","latency_90", "audit_success_count", "total_audit_count", "audit_success_ratio", "uptime_success_count", "total_uptime_count", "uptime_ratio", "created_at", "updated_at", "last_contact_success", "last_contact_failure") VALUES (E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014', '127.0.0.1:55517', 0, 4, '', '', -1, -1, 0, 1, 0, '', 'epoch', false, 0, 0, 0, 1, 0, 0, 1, '2019-02-14 08:07:31.028103+00', '2019-02-14 08:07:31.108963+00', 'epoch', 'epoch'); + + +INSERT INTO "projects"("id", "name", "description", "created_at") VALUES (E'\\022\\217/\\014\\376!K\\023\\276\\031\\311}m\\236\\205\\300'::bytea, 'ProjectName', 'projects description', '2019-02-14 08:28:24.254934+00'); +INSERT INTO "api_keys"("id", "project_id", "key", "name", "created_at") VALUES (E'\\334/\\302;\\225\\355O\\323\\276f\\247\\354/6\\241\\033'::bytea, E'\\022\\217/\\014\\376!K\\023\\276\\031\\311}m\\236\\205\\300'::bytea, E'\\000]\\326N \\343\\270L\\327\\027\\337\\242\\240\\322mOl\\0318\\251.P I'::bytea, 'key 2', '2019-02-14 08:28:24.267934+00'); + +INSERT INTO "users"("id", "full_name", "short_name", "email", "password_hash", "status", "created_at") VALUES (E'\\363\\311\\033w\\222\\303Ci\\265\\343U\\303\\312\\204",'::bytea, 'Noahson', 'William', '1email1@ukr.net', E'some_readable_hash'::bytea, 1, '2019-02-14 08:28:24.614594+00'); +INSERT INTO "projects"("id", "name", "description", "created_at") VALUES (E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014'::bytea, 'projName1', 'Test project 1', '2019-02-14 08:28:24.636949+00'); +INSERT INTO "project_members"("member_id", "project_id", "created_at") VALUES (E'\\363\\311\\033w\\222\\303Ci\\265\\343U\\303\\312\\204",'::bytea, E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014'::bytea, '2019-02-14 08:28:24.677953+00'); + +INSERT INTO "bwagreements"("serialnum", "storage_node_id", "action", "total", "created_at", "expires_at", "uplink_id") VALUES ('8fc0ceaa-984c-4d52-bcf4-b5429e1e35e812FpiifDbcJkePa12jxjDEutKrfLmwzT7sz2jfVwpYqgtM8B74c', E'\\245Z[/\\333\\022\\011\\001\\036\\003\\204\\005\\032.\\206\\333E\\261\\342\\227=y,}aRaH6\\240\\370\\000'::bytea, 1, 666, '2019-02-14 15:09:54.420181+00', '2019-02-14 16:09:54+00', E'\\253Z+\\374eFm\\245$\\036\\206\\335\\247\\263\\350x\\\\\\304+\\364\\343\\364+\\276fIJQ\\361\\014\\232\\000'::bytea); +INSERT INTO "irreparabledbs" ("segmentpath", "segmentdetail", "pieces_lost_count", "seg_damaged_unix_sec", "repair_attempt_count") VALUES ('\x49616d5365676d656e746b6579696e666f30', '\x49616d5365676d656e7464657461696c696e666f30', 10, 1550159554, 10); + +INSERT INTO "injuredsegments" ("path", "data") VALUES ('0', '\x0a0130120100'); +INSERT INTO "injuredsegments" ("path", "data") VALUES ('here''s/a/great/path', '\x0a136865726527732f612f67726561742f70617468120a0102030405060708090a'); +INSERT INTO "injuredsegments" ("path", "data") VALUES ('yet/another/cool/path', '\x0a157965742f616e6f746865722f636f6f6c2f70617468120a0102030405060708090a'); +INSERT INTO "injuredsegments" ("path", "data") VALUES ('so/many/iconic/paths/to/choose/from', '\x0a23736f2f6d616e792f69636f6e69632f70617468732f746f2f63686f6f73652f66726f6d120a0102030405060708090a'); + +INSERT INTO "certrecords" VALUES (E'0Y0\\023\\006\\007*\\206H\\316=\\002\\001\\006\\010*\\206H\\316=\\003\\001\\007\\003B\\000\\004\\360\\267\\227\\377\\253u\\222\\337Y\\324C:GQ\\010\\277v\\010\\315D\\271\\333\\337.\\203\\023=C\\343\\014T%6\\027\\362?\\214\\326\\017U\\334\\000\\260\\224\\260J\\221\\304\\331F\\304\\221\\236zF,\\325\\326l\\215\\306\\365\\200\\022', E'L\\301|\\200\\247}F|1\\320\\232\\037n\\335\\241\\206\\244\\242\\207\\204.\\253\\357\\326\\352\\033Dt\\202`\\022\\325', '2019-02-14 08:07:31.335028+00'); + +INSERT INTO "bucket_usages" ("id", "bucket_id", "rollup_end_time", "remote_stored_data", "inline_stored_data", "remote_segments", "inline_segments", "objects", "metadata_size", "repair_egress", "get_egress", "audit_egress") VALUES (E'\\153\\313\\233\\074\\327\\177\\136\\070\\346\\001",'::bytea, E'\\366\\146\\032\\321\\316\\161\\070\\133\\302\\271",'::bytea, '2019-03-06 08:28:24.677953+00', 10, 11, 12, 13, 14, 15, 16, 17, 18); + +INSERT INTO "registration_tokens" ("secret", "owner_id", "project_limit", "created_at") VALUES (E'\\070\\127\\144\\013\\332\\344\\102\\376\\306\\056\\303\\130\\106\\132\\321\\276\\321\\274\\170\\264\\054\\333\\221\\116\\154\\221\\335\\070\\220\\146\\344\\216'::bytea, null, 1, '2019-02-14 08:28:24.677953+00'); + +INSERT INTO "serial_numbers" ("id", "serial_number", "bucket_id", "expires_at") VALUES (1, E'0123456701234567'::bytea, E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014/testbucket'::bytea, '2019-03-06 08:28:24.677953+00'); +INSERT INTO "used_serials" ("serial_number_id", "storage_node_id") VALUES (1, E'\\006\\223\\250R\\221\\005\\365\\377v>0\\266\\365\\216\\255?\\347\\244\\371?2\\264\\262\\230\\007<\\001\\262\\263\\237\\247n'); + +INSERT INTO "storagenode_bandwidth_rollups" ("storagenode_id", "interval_start", "interval_seconds", "action", "allocated", "settled") VALUES (E'\\006\\223\\250R\\221\\005\\365\\377v>0\\266\\365\\216\\255?\\347\\244\\371?2\\264\\262\\230\\007<\\001\\262\\263\\237\\247n', '2019-03-06 08:00:00.000000+00', 3600, 1, 1024, 2024); +INSERT INTO "storagenode_storage_tallies" VALUES (1, E'\\3510\\323\\225"~\\036<\\342\\330m\\0253Jhr\\246\\233K\\246#\\2303\\351\\256\\275j\\212UM\\362\\207', '2019-02-14 08:16:57.812849+00', 1000); + +INSERT INTO "bucket_bandwidth_rollups" ("bucket_name", "project_id", "interval_start", "interval_seconds", "action", "inline", "allocated", "settled") VALUES (E'testbucket'::bytea, E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014'::bytea,'2019-03-06 08:00:00.000000+00', 3600, 1, 1024, 2024, 3024); +INSERT INTO "bucket_storage_tallies" ("bucket_name", "project_id", "interval_start", "inline", "remote", "remote_segments_count", "inline_segments_count", "object_count", "metadata_size") VALUES (E'testbucket'::bytea, E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014'::bytea,'2019-03-06 08:00:00.000000+00', 4024, 5024, 0, 0, 0, 0); + +-- NEW DATA -- + +INSERT INTO "reset_password_tokens" ("secret", "owner_id", "created_at") VALUES (E'\\070\\127\\144\\013\\332\\344\\102\\376\\306\\056\\303\\130\\106\\132\\321\\276\\321\\274\\170\\264\\054\\333\\221\\116\\154\\221\\335\\070\\220\\146\\344\\216'::bytea, E'\\363\\311\\033w\\222\\303Ci\\265\\343U\\303\\312\\204",'::bytea, '2019-05-08 08:28:24.677953+00'); diff --git a/web/satellite/static/emails/Forgot.html b/web/satellite/static/emails/Forgot.html index c755c0b55..5c016649b 100644 --- a/web/satellite/static/emails/Forgot.html +++ b/web/satellite/static/emails/Forgot.html @@ -532,7 +532,8 @@ To reset your password, click the following link and follow the instructions. &#

Didn’t request this change?


If you didn’t request a new password - let us know

+ let us know

+

Please Note: After clicking on «Let us Know» we will automatically deactivate this reset password link and redirect you to our Support Desk.


diff --git a/web/satellite/static/resetPassword/success.css b/web/satellite/static/resetPassword/success.css new file mode 100644 index 000000000..7a31c7c81 --- /dev/null +++ b/web/satellite/static/resetPassword/success.css @@ -0,0 +1,66 @@ +/*Copyright (C) 2019 Storj Labs, Inc.*/ +/*See LICENSE for copying information.*/ + +.container { + position: fixed; + top: 0; + left: 0; + z-index: 1000; + width: 100%; + height: 100vh; + background-color: #fff; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + +} + +a { + text-decoration: none; +} + + .container__title { + font-family: 'font', sans-serif; + font-weight: bold; + font-size: 32px; + line-height: 39px; + } + + .container__info { + width: 566px; + text-align: center; + font-family: 'font', sans-serif; + font-size: 18px; + line-height: 26px; + } + + .container__button { + display: flex; + align-items: center; + justify-content: center; + background-color: #2683FF; + border-radius: 6px; + cursor: pointer; + width: 216px; + height: 48px; + margin-top: 30px; + } + + .container__button p { + font-family: 'font', sans-serif; + font-weight: bold; + font-size: 16px; + line-height: 23px; + color: #fff; + } + + .container__button:hover { + box-shadow: 0px 4px 20px rgba(35, 121, 236, 0.4); + } diff --git a/web/satellite/static/resetPassword/success.html b/web/satellite/static/resetPassword/success.html new file mode 100644 index 000000000..f5dcfc3d8 --- /dev/null +++ b/web/satellite/static/resetPassword/success.html @@ -0,0 +1,34 @@ + + + + + + + + Tardigrade Satellite - Mars + + + + + +
+ + + + + + + + + + +

You have successfully changed your password.

+

Now you can use new credentials to work with your Satellite Account.

+ +
+

Go To Log In

+
+
+
+ +