summaryrefslogtreecommitdiff
path: root/internal/storage
diff options
context:
space:
mode:
authorJames Elliott <james-d-elliott@users.noreply.github.com>2023-10-26 19:41:06 +1100
committerJames Elliott <james-d-elliott@users.noreply.github.com>2024-03-04 20:29:11 +1100
commit2a388194fbf56e8c030dc734f980dc223760b8d9 (patch)
tree6dd17b6e4cbe3d1c0f6ab556632ae6fd2f68d145 /internal/storage
parentf81b414147014a8096ef995ea691c0010a4aab67 (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')
-rw-r--r--internal/storage/migrations/V0014.RevokeResetPasswordJWT.mysql.down.sql3
-rw-r--r--internal/storage/migrations/V0014.RevokeResetPasswordJWT.mysql.up.sql3
-rw-r--r--internal/storage/migrations/V0014.RevokeResetPasswordJWT.postgres.down.sql3
-rw-r--r--internal/storage/migrations/V0014.RevokeResetPasswordJWT.postgres.up.sql3
-rw-r--r--internal/storage/migrations/V0014.RevokeResetPasswordJWT.sqlite.down.sql2
-rw-r--r--internal/storage/migrations/V0014.RevokeResetPasswordJWT.sqlite.up.sql3
-rw-r--r--internal/storage/migrations_test.go2
-rw-r--r--internal/storage/provider.go7
-rw-r--r--internal/storage/sql_provider.go27
-rw-r--r--internal/storage/sql_provider_backend_postgres.go3
-rw-r--r--internal/storage/sql_provider_queries.go7
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 (