diff options
| author | James Elliott <james-d-elliott@users.noreply.github.com> | 2023-10-26 19:41:06 +1100 | 
|---|---|---|
| committer | James Elliott <james-d-elliott@users.noreply.github.com> | 2024-03-04 20:29:11 +1100 | 
| commit | 2a388194fbf56e8c030dc734f980dc223760b8d9 (patch) | |
| tree | 6dd17b6e4cbe3d1c0f6ab556632ae6fd2f68d145 /internal/storage | |
| parent | f81b414147014a8096ef995ea691c0010a4aab67 (diff) | |
feat(web): revoke reset password tokens
This adds functionality to the frontend to revoke the Reset Password JWT's.
Closes #136
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
Diffstat (limited to 'internal/storage')
11 files changed, 59 insertions, 4 deletions
diff --git a/internal/storage/migrations/V0014.RevokeResetPasswordJWT.mysql.down.sql b/internal/storage/migrations/V0014.RevokeResetPasswordJWT.mysql.down.sql new file mode 100644 index 000000000..20912277e --- /dev/null +++ b/internal/storage/migrations/V0014.RevokeResetPasswordJWT.mysql.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE identity_verification +	DROP COLUMN revoked_ip, +    DROP COLUMN revoked; diff --git a/internal/storage/migrations/V0014.RevokeResetPasswordJWT.mysql.up.sql b/internal/storage/migrations/V0014.RevokeResetPasswordJWT.mysql.up.sql new file mode 100644 index 000000000..e57858bf4 --- /dev/null +++ b/internal/storage/migrations/V0014.RevokeResetPasswordJWT.mysql.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE identity_verification +	ADD COLUMN revoked TIMESTAMP NULL DEFAULT NULL, +    ADD COLUMN revoked_ip VARCHAR(39) NULL DEFAULT NULL; diff --git a/internal/storage/migrations/V0014.RevokeResetPasswordJWT.postgres.down.sql b/internal/storage/migrations/V0014.RevokeResetPasswordJWT.postgres.down.sql new file mode 100644 index 000000000..20912277e --- /dev/null +++ b/internal/storage/migrations/V0014.RevokeResetPasswordJWT.postgres.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE identity_verification +	DROP COLUMN revoked_ip, +    DROP COLUMN revoked; diff --git a/internal/storage/migrations/V0014.RevokeResetPasswordJWT.postgres.up.sql b/internal/storage/migrations/V0014.RevokeResetPasswordJWT.postgres.up.sql new file mode 100644 index 000000000..7184986ab --- /dev/null +++ b/internal/storage/migrations/V0014.RevokeResetPasswordJWT.postgres.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE identity_verification +	ADD COLUMN revoked TIMESTAMP WITH TIME ZONE NULL DEFAULT NULL, +    ADD COLUMN revoked_ip VARCHAR(39) NULL DEFAULT NULL; diff --git a/internal/storage/migrations/V0014.RevokeResetPasswordJWT.sqlite.down.sql b/internal/storage/migrations/V0014.RevokeResetPasswordJWT.sqlite.down.sql new file mode 100644 index 000000000..d6661f210 --- /dev/null +++ b/internal/storage/migrations/V0014.RevokeResetPasswordJWT.sqlite.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE identity_verification DROP COLUMN revoked_ip; +ALTER TABLE identity_verification DROP COLUMN revoked; diff --git a/internal/storage/migrations/V0014.RevokeResetPasswordJWT.sqlite.up.sql b/internal/storage/migrations/V0014.RevokeResetPasswordJWT.sqlite.up.sql new file mode 100644 index 000000000..d5686c8b6 --- /dev/null +++ b/internal/storage/migrations/V0014.RevokeResetPasswordJWT.sqlite.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE identity_verification ADD COLUMN revoked DATETIME NULL DEFAULT NULL; +ALTER TABLE identity_verification ADD COLUMN revoked_ip VARCHAR(39) NULL DEFAULT NULL; + diff --git a/internal/storage/migrations_test.go b/internal/storage/migrations_test.go index b2b164487..1d71ff836 100644 --- a/internal/storage/migrations_test.go +++ b/internal/storage/migrations_test.go @@ -9,7 +9,7 @@ import (  const (  	// This is the latest schema version for the purpose of tests. -	LatestVersion = 13 +	LatestVersion = 14  )  func TestShouldObtainCorrectUpMigrations(t *testing.T) { diff --git a/internal/storage/provider.go b/internal/storage/provider.go index 245d8b5c4..a0f23627d 100644 --- a/internal/storage/provider.go +++ b/internal/storage/provider.go @@ -131,9 +131,16 @@ type Provider interface {  	// ConsumeIdentityVerification marks an identity verification record in the storage provider as consumed.  	ConsumeIdentityVerification(ctx context.Context, jti string, ip model.NullIP) (err error) +	// RevokeIdentityVerification marks an identity verification record in the storage provider as revoked. +	RevokeIdentityVerification(ctx context.Context, jti string, ip model.NullIP) (err error) +  	// FindIdentityVerification checks if an identity verification record is in the storage provider and active.  	FindIdentityVerification(ctx context.Context, jti string) (found bool, err error) +	// LoadIdentityVerification loads an Identity Verification but does not do any validation. +	// For easy validation you should use FindIdentityVerification which ensures the JWT is still valid. +	LoadIdentityVerification(ctx context.Context, jti string) (verification *model.IdentityVerification, err error) +  	/*  		Implementation for Identity Verification (OTP).  	*/ diff --git a/internal/storage/sql_provider.go b/internal/storage/sql_provider.go index d53425485..66f8a7795 100644 --- a/internal/storage/sql_provider.go +++ b/internal/storage/sql_provider.go @@ -40,6 +40,7 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa  		sqlInsertIdentityVerification:  fmt.Sprintf(queryFmtInsertIdentityVerification, tableIdentityVerification),  		sqlConsumeIdentityVerification: fmt.Sprintf(queryFmtConsumeIdentityVerification, tableIdentityVerification), +		sqlRevokeIdentityVerification:  fmt.Sprintf(queryFmtRevokeIdentityVerification, tableIdentityVerification),  		sqlSelectIdentityVerification:  fmt.Sprintf(queryFmtSelectIdentityVerification, tableIdentityVerification),  		sqlInsertOneTimeCode:            fmt.Sprintf(queryFmtInsertOTC, tableOneTimeCode), @@ -171,6 +172,7 @@ type SQLProvider struct {  	// Table: identity_verification.  	sqlInsertIdentityVerification  string  	sqlConsumeIdentityVerification string +	sqlRevokeIdentityVerification  string  	sqlSelectIdentityVerification  string  	// Table: one_time_code. @@ -776,6 +778,15 @@ func (p *SQLProvider) ConsumeIdentityVerification(ctx context.Context, jti strin  	return nil  } +// RevokeIdentityVerification marks an identity verification record in the storage provider as revoked. +func (p *SQLProvider) RevokeIdentityVerification(ctx context.Context, jti string, ip model.NullIP) (err error) { +	if _, err = p.db.ExecContext(ctx, p.sqlRevokeIdentityVerification, ip, jti); err != nil { +		return fmt.Errorf("error updating identity verification: %w", err) +	} + +	return nil +} +  // FindIdentityVerification checks if an identity verification record is in the storage provider and active.  func (p *SQLProvider) FindIdentityVerification(ctx context.Context, jti string) (found bool, err error) {  	verification := model.IdentityVerification{} @@ -788,7 +799,9 @@ func (p *SQLProvider) FindIdentityVerification(ctx context.Context, jti string)  	}  	switch { -	case verification.Consumed.Valid: +	case verification.RevokedAt.Valid: +		return false, fmt.Errorf("the token has been revoked") +	case verification.ConsumedAt.Valid:  		return false, fmt.Errorf("the token has already been consumed")  	case verification.ExpiresAt.Before(time.Now()):  		return false, fmt.Errorf("the token expired %s ago", time.Since(verification.ExpiresAt)) @@ -797,6 +810,18 @@ func (p *SQLProvider) FindIdentityVerification(ctx context.Context, jti string)  	}  } +// LoadIdentityVerification loads an Identity Verification but does not do any validation. +// For easy validation you should use FindIdentityVerification which ensures the JWT is still valid. +func (p *SQLProvider) LoadIdentityVerification(ctx context.Context, jti string) (verification *model.IdentityVerification, err error) { +	verification = &model.IdentityVerification{} + +	if err = p.db.GetContext(ctx, verification, p.sqlSelectIdentityVerification, jti); err != nil { +		return nil, fmt.Errorf("error selecting identity verification: %w", err) +	} + +	return verification, nil +} +  // SaveOneTimeCode saves a One-Time Code to the storage provider after generating the signature which is returned  // along with any error.  func (p *SQLProvider) SaveOneTimeCode(ctx context.Context, code model.OneTimeCode) (signature string, err error) { diff --git a/internal/storage/sql_provider_backend_postgres.go b/internal/storage/sql_provider_backend_postgres.go index cc9adbd8e..4a049cffd 100644 --- a/internal/storage/sql_provider_backend_postgres.go +++ b/internal/storage/sql_provider_backend_postgres.go @@ -47,9 +47,10 @@ func NewPostgreSQLProvider(config *schema.Configuration, caCertPool *x509.CertPo  	provider.sqlSelectUserOpaqueIdentifier = provider.db.Rebind(provider.sqlSelectUserOpaqueIdentifier)  	provider.sqlSelectUserOpaqueIdentifierBySignature = provider.db.Rebind(provider.sqlSelectUserOpaqueIdentifierBySignature) -	provider.sqlSelectIdentityVerification = provider.db.Rebind(provider.sqlSelectIdentityVerification)  	provider.sqlInsertIdentityVerification = provider.db.Rebind(provider.sqlInsertIdentityVerification)  	provider.sqlConsumeIdentityVerification = provider.db.Rebind(provider.sqlConsumeIdentityVerification) +	provider.sqlRevokeIdentityVerification = provider.db.Rebind(provider.sqlRevokeIdentityVerification) +	provider.sqlSelectIdentityVerification = provider.db.Rebind(provider.sqlSelectIdentityVerification)  	provider.sqlInsertOneTimeCode = provider.db.Rebind(provider.sqlInsertOneTimeCode)  	provider.sqlConsumeOneTimeCode = provider.db.Rebind(provider.sqlConsumeOneTimeCode) diff --git a/internal/storage/sql_provider_queries.go b/internal/storage/sql_provider_queries.go index fb9276458..bff832dbd 100644 --- a/internal/storage/sql_provider_queries.go +++ b/internal/storage/sql_provider_queries.go @@ -57,7 +57,7 @@ const (  const (  	queryFmtSelectIdentityVerification = ` -		SELECT id, jti, iat, issued_ip, exp, username, action, consumed, consumed_ip +		SELECT id, jti, iat, issued_ip, exp, username, action, consumed, consumed_ip, revoked, revoked_ip  		FROM %s  		WHERE jti = ?;` @@ -69,6 +69,11 @@ const (  		UPDATE %s  		SET consumed = CURRENT_TIMESTAMP, consumed_ip = ?  		WHERE jti = ?;` + +	queryFmtRevokeIdentityVerification = ` +		UPDATE %s +		SET revoked = CURRENT_TIMESTAMP, revoked_ip = ? +		WHERE jti = ?;`  )  const (  | 
