Skip to content

Commit

Permalink
Added account deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
steve-mir committed Jun 15, 2024
1 parent 94db7b7 commit ef59a42
Show file tree
Hide file tree
Showing 14 changed files with 503 additions and 5 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"Msgf",
"pgtype",
"pgxpool",
"Timestamptz",
"zerolog"
]
}
20 changes: 20 additions & 0 deletions db/migration/000003_security.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
ALTER TABLE "password_reset_requests" DROP CONSTRAINT IF EXISTS "password_reset_requests_user_id_fkey";
ALTER TABLE "two_factor_secrets" DROP CONSTRAINT IF EXISTS "two_factor_secrets_user_id_fkey";
ALTER TABLE "two_factor_revocation" DROP CONSTRAINT IF EXISTS "two_factor_revocation_user_id_fkey";
ALTER TABLE "two_factor_backup_codes" DROP CONSTRAINT IF EXISTS "two_factor_backup_codes_user_id_fkey";

ALTER TABLE "account_recovery_requests" DROP CONSTRAINT IF EXISTS "account_recovery_requests_user_id_fkey";


-- Drop indexes
DROP INDEX IF EXISTS "password_reset_requests_email_user_id_token_expires_at_idx";
DROP INDEX IF EXISTS "account_recovery_requests_user_id_idx";


-- Drop tables
DROP TABLE IF EXISTS "two_factor_backup_codes";
DROP TABLE IF EXISTS "two_factor_revocation";
DROP TABLE IF EXISTS "two_factor_secrets";
DROP TABLE IF EXISTS "password_reset_requests";

DROP TABLE IF EXISTS "account_recovery_requests";
57 changes: 57 additions & 0 deletions db/migration/000003_security.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
CREATE TABLE "password_reset_requests" (
"id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"user_id" uuid NOT NULL,
"email" varchar NOT NULL,
"token" varchar NOT NULL UNIQUE,
"used" boolean DEFAULT false,
"created_at" timestamptz DEFAULT (now()),
"expires_at" timestamptz NOT NULL
);

CREATE TABLE "two_factor_secrets" (
"id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"user_id" uuid NOT NULL, -- UNIQUE,
"secret_key" varchar NOT NULL,
"is_active" boolean DEFAULT true
);

CREATE TABLE "two_factor_revocation" (
"id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"user_id" uuid NOT NULL,
"revoked_at" timestamptz,
"revocation_reason" varchar
-- "revoked_by" UUID REFERENCES users(id)
);

CREATE TABLE "two_factor_backup_codes" (
"id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"user_id" uuid NOT NULL,
"code" varchar NOT NULL,
"used" boolean DEFAULT false
);

CREATE TABLE "account_recovery_requests" (
"id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"user_id" uuid NOT NULL,
"email" varchar NOT NULL,
"used" BOOLEAN DEFAULT false,
"recovery_token" varchar unique not null,
"requested_at" timestamptz DEFAULT (now()),
"expires_at" timestamptz not null,
"completed_at" timestamptz
);

CREATE INDEX ON "password_reset_requests" ( "user_id","email", "token", "expires_at");

CREATE INDEX ON "account_recovery_requests" ( "user_id", "recovery_token");


ALTER TABLE "password_reset_requests" ADD FOREIGN KEY ("user_id") REFERENCES "authentications" ("id");

ALTER TABLE "two_factor_secrets" ADD FOREIGN KEY ("user_id") REFERENCES "authentications" ("id");

ALTER TABLE "two_factor_revocation" ADD FOREIGN KEY ("user_id") REFERENCES "authentications" ("id");

ALTER TABLE "two_factor_backup_codes" ADD FOREIGN KEY ("user_id") REFERENCES "authentications" ("id");

ALTER TABLE "account_recovery_requests" ADD FOREIGN KEY ("user_id") REFERENCES "authentications" ("id");
11 changes: 11 additions & 0 deletions db/query/delete_user.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- name: CreateUserDeleteRequest :exec
INSERT INTO account_recovery_requests (
user_id, email, recovery_token, expires_at, completed_at
)
VALUES ($1, $2, $3, $4, $5);

-- name: GetUserFromDeleteReqByToken :one
SELECT * FROM account_recovery_requests WHERE recovery_token = $1 LIMIT 1;

-- name: MarkDeleteAsUsedByToken :exec
UPDATE account_recovery_requests SET used = true, completed_at = now() WHERE recovery_token = $1;
69 changes: 69 additions & 0 deletions db/sqlc/delete_user.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions db/sqlc/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions db/sqlc/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions internal/app/auth/api/delete_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package api

import (
"net/http"

"github.com/gin-gonic/gin"
"github.com/steve-mir/bukka_backend/internal/app/auth/middlewares"
"github.com/steve-mir/bukka_backend/internal/app/auth/services"
"github.com/steve-mir/bukka_backend/token"
)

func (s *Server) deleteAccount(ctx *gin.Context) {
authPayload := ctx.MustGet(middlewares.AuthorizationPayloadKey).(*token.Payload)

var req services.DeleteAccountReq
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err))
return
}

err := services.DeleteAccountRequest(ctx, req.Password, s.store, authPayload.Subject)
if err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err))
return
}

ctx.JSON(http.StatusOK, services.GenericRes{
Msg: "Account deleted successfully",
})

}

func (s *Server) requestAccountRecovery(ctx *gin.Context) {
var req services.AccountRecoveryReq
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err))
return
}

err := services.AccRecoveryRequest(ctx, s.store, s.taskDistributor, req.Email)
if err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err))
return
}

ctx.JSON(http.StatusOK, services.GenericRes{
Msg: "URL has been sent to your email",
})

}

func (s *Server) completeAccountRecovery(ctx *gin.Context) {
token := ctx.Query("token")
err := services.AccountRecovery(ctx, s.connPool, s.store, token)
if err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err))
return
}

ctx.JSON(http.StatusOK, services.GenericRes{
Msg: "Account recovered, you can now login to access your account.",
})

}
2 changes: 0 additions & 2 deletions internal/app/auth/api/resend_verification_email.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package api

import (
"log"
"net/http"

"github.com/gin-gonic/gin"
Expand All @@ -12,7 +11,6 @@ import (

func (s *Server) resendVerificationEmail(ctx *gin.Context) {
authPayload := ctx.MustGet(middlewares.AuthorizationPayloadKey).(*token.Payload)
log.Println(authPayload)
err := services.ReSendVerificationEmail(s.store, ctx, s.taskDistributor, authPayload.Subject, authPayload.Email)
if err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err))
Expand Down
7 changes: 4 additions & 3 deletions internal/app/auth/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ func (server *Server) setupRouter() {
authRoutes := router.Group("/").Use(middlewares.AuthMiddlerWare(server.config))
authRoutes.GET(baseUrl+"resend_verification", server.resendVerificationEmail)

authRoutes.DELETE(baseUrl+"delete_account", server.deleteAccount)
router.POST(baseUrl+"request_account_recovery", server.requestAccountRecovery)
router.GET(baseUrl+"recover_account", server.completeAccountRecovery)

// router.POST(baseUrl+"change_password", server.register)
// router.POST(baseUrl+"request_reset_password", server.register)
// router.POST(baseUrl+"reset_password", server.register)
Expand All @@ -57,9 +61,6 @@ func (server *Server) setupRouter() {
// router.POST(baseUrl+"confirm_change_phone", server.register)
// router.POST(baseUrl+"change_username", server.register)
// router.PATCH(baseUrl+"update_user", server.register)
// router.POST(baseUrl+"delete_account", server.register)
// router.POST(baseUrl+"request_account_recovery", server.register)
// router.POST(baseUrl+"recover_account", server.register)
// router.POST(baseUrl+"register_sso", server.register)
// router.POST(baseUrl+"login_sso", server.register)
// router.POST(baseUrl+"register_mfa", server.register)
Expand Down
12 changes: 12 additions & 0 deletions internal/app/auth/services/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ type VerifyEmailReq struct {
Token string `json:"token" binding:"required"` // TODO: Add verification to allow only digits within the appropriate length
}

type DeleteAccountReq struct {
Password string `json:"password" binding:"required,passwordValidator"`
}

type AccountRecoveryReq struct {
Email string `json:"email" binding:"required,emailValidator"`
}

// Responses
type AuthTokenResp struct {
AccessToken string `json:"access_token"`
Expand All @@ -43,3 +51,7 @@ type VerifyEmailRes struct {
Msg string `json:"msg"`
Verified bool `json:"verified"`
}

type GenericRes struct {
Msg string `json:"msg"`
}
Loading

0 comments on commit ef59a42

Please sign in to comment.