Đặt lại mật khẩu

Mô tả tổng quan

Tính năng Đặt lại mật khẩu cho phép người dùng khôi phục quyền truy cập vào tài khoản khi họ quên mật khẩu. Quy trình này bao gồm việc yêu cầu liên kết đặt lại mật khẩu, nhận email với token được bảo mật và sử dụng token đó để đặt mật khẩu mới. Hệ thống đảm bảo bảo mật bằng cách tạo token có thời hạn, xác minh quyền sở hữu email và yêu cầu tạo mật khẩu mới an toàn đáp ứng yêu cầu của hệ thống.

API: Password Reset API

Sơ đồ hoạt động

---
config:
  theme: base
  layout: dagre
  flowchart:
    curve: linear
    htmlLabels: true
  themeVariables:
    edgeLabelBackground: "transparent"
---
flowchart TB
    %% Main components
    Client[Ứng dụng Client]
    ForgotPasswordController[ForgotPasswordController]
    ResetPasswordController[ResetPasswordController]
    AuthService(AuthService)
    EmailService((EmailService))
    Firebase((Firebase Auth))
    UserDB[(users)]
    ResetTokenDB[(password_resets)]

    Client --- Step1[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>1</span>
            <p style='margin-top: 8px'>Yêu cầu đặt lại mật khẩu</p>
        </div>
    ]
    Step1 --> ForgotPasswordController

    ForgotPasswordController --- Step2[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>2</span>
            <p style='margin-top: 8px'>Xác thực yêu cầu</p>
        </div>
    ]
    Step2 --> AuthService

    AuthService --- Step3[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>3</span>
            <p style='margin-top: 8px'>Kiểm tra User tồn tại</p>
        </div>
    ]
    Step3 --> UserDB

    AuthService --- Step4[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>4</span>
            <p style='margin-top: 8px'>Tạo Reset Token</p>
        </div>
    ]
    Step4 --> ResetTokenDB

    AuthService --- Step5[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>5</span>
            <p style='margin-top: 8px'>Gửi Reset Email</p>
        </div>
    ]
    Step5 --> EmailService

    EmailService --- Step6[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>6</span>
            <p style='margin-top: 8px'>Trả về Response</p>
        </div>
    ]
    Step6 --> ForgotPasswordController

    ForgotPasswordController --- Step7[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>7</span>
            <p style='margin-top: 8px'>Trả về Response</p>
        </div>
    ]
    Step7 --> Client

    Client --- Step8[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>8</span>
            <p style='margin-top: 8px'>Gửi form đặt lại</p>
        </div>
    ]
    Step8 --> ResetPasswordController

    ResetPasswordController --- Step9[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>9</span>
            <p style='margin-top: 8px'>Xác minh Token</p>
        </div>
    ]
    Step9 --> AuthService

    AuthService --- Step10[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>10</span>
            <p style='margin-top: 8px'>Cập nhật mật khẩu</p>
        </div>
    ]
    Step10 --> Firebase

    AuthService --- Step11[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>11</span>
            <p style='margin-top: 8px'>Xóa Token</p>
        </div>
    ]
    Step11 --> ResetTokenDB

    AuthService --- Step12[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>12</span>
            <p style='margin-top: 8px'>Trả về Response</p>
        </div>
    ]
    Step12 --> ResetPasswordController

    ResetPasswordController --- Step13[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>13</span>
            <p style='margin-top: 8px'>Trả về Response</p>
        </div>
    ]
    Step13 --> Client

    %% Styling
    style Client fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
    style ForgotPasswordController fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
    style ResetPasswordController fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
    style AuthService fill:#f0f8e6,stroke:#339933,stroke-width:2px
    style EmailService fill:#fcd9d9,stroke:#cc3333,stroke-width:2px
    style Firebase fill:#fcd9d9,stroke:#cc3333,stroke-width:2px
    style UserDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
    style ResetTokenDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
    style Step1 fill:transparent,stroke:transparent,stroke-width:1px
    style Step2 fill:transparent,stroke:transparent,stroke-width:1px
    style Step3 fill:transparent,stroke:transparent,stroke-width:1px
    style Step4 fill:transparent,stroke:transparent,stroke-width:1px
    style Step5 fill:transparent,stroke:transparent,stroke-width:1px
    style Step6 fill:transparent,stroke:transparent,stroke-width:1px
    style Step7 fill:transparent,stroke:transparent,stroke-width:1px
    style Step8 fill:transparent,stroke:transparent,stroke-width:1px
    style Step9 fill:transparent,stroke:transparent,stroke-width:1px
    style Step10 fill:transparent,stroke:transparent,stroke-width:1px
    style Step11 fill:transparent,stroke:transparent,stroke-width:1px
    style Step12 fill:transparent,stroke:transparent,stroke-width:1px
    style Step13 fill:transparent,stroke:transparent,stroke-width:1px

Tài liệu trường hợp

Trường hợp 1: Yêu cầu đặt lại mật khẩu

Mô tả

Người dùng yêu cầu liên kết đặt lại mật khẩu bằng cách cung cấp địa chỉ email của họ.

Sơ đồ tuần tự

sequenceDiagram
    participant User
    participant API as ForgotPasswordController
    participant Service as AuthService
    participant Email as EmailService
    participant UserDB as Database
    participant TokenDB as Database

    Note over User,API: Bước 1: Gửi yêu cầu đặt lại
    User->>API: POST /api/v1/general/auth/forgot (với email và URL)
    
    Note over API,Service: Bước 2: Xử lý yêu cầu
    API->>Service: sendResetPassEmail(email, url)
    
    Note over Service,UserDB: Bước 3: Kiểm tra User
    Service->>UserDB: findByEmail(email)
    UserDB-->>Service: Trả về dữ liệu user
    
    Note over Service,TokenDB: Bước 4: Tạo Reset Token
    Service->>TokenDB: createToken(email)
    TokenDB-->>Service: Trả về reset token
    
    Note over Service,Email: Bước 5: Gửi Reset Email
    Service->>Email: sendPasswordResetLink(email, token, url)
    Email-->>Service: Trạng thái gửi email
    
    Note over API,User: Bước 6: Trả về Response
    API-->>User: 200 OK với thông báo thành công

Các bước

Bước 1: Gửi yêu cầu đặt lại

  • Mô tả: Người dùng gửi địa chỉ email để đặt lại mật khẩu
  • Request: POST /api/v1/general/auth/forgot
  • Body Parameters:
    • email: Địa chỉ email đã đăng ký của người dùng
    • url: URL phía client cho form đặt lại mật khẩu
  • Validation:
    • Xác thực định dạng email
    • Validation các trường bắt buộc
    • Kiểm tra giới hạn tốc độ (3 yêu cầu mỗi giờ)

Bước 2: Xử lý yêu cầu

  • Mô tả: Controller chuyển yêu cầu đã xác thực cho service
  • Hành động:
    • Trích xuất email và reset URL
    • Gọi authentication service cho quy trình đặt lại mật khẩu
    • Ghi log yêu cầu đặt lại

Bước 3: Kiểm tra User

  • Mô tả: Xác minh user tồn tại trong hệ thống
  • Hành động:
    • Truy vấn database cho user với email được cung cấp
    • Xác minh tài khoản user đang hoạt động
    • Kiểm tra nếu user có Firebase authentication

Bước 4: Tạo Reset Token

  • Mô tả: Tạo token an toàn để đặt lại mật khẩu
  • Hành động:
    • Tạo token bảo mật về mặt mật mã
    • Lưu token với thời gian hết hạn (60 phút)
    • Liên kết token với email user
    • Xóa bất kỳ token hiện có nào cho email này

Bước 5: Gửi Reset Email

  • Mô tả: Gửi hướng dẫn đặt lại mật khẩu qua email
  • Hành động:
    • Tạo email với hướng dẫn đặt lại
    • Bao gồm reset URL với token
    • Đặt ưu tiên email cao
    • Gửi email đến địa chỉ của user
    • Ghi log trạng thái gửi email

Bước 6: Trả về Response

  • Mô tả: Thông báo cho user về trạng thái gửi email
  • Response:
    • Thành công: 200 OK với thông báo thành công
    • Lỗi: Mã lỗi phù hợp với thông báo

Bảng dữ liệu liên quan

erDiagram
    users {
        bigint id PK "Khóa chính tự động tăng"
        string name "Tên đầy đủ của người dùng"
        string email "Địa chỉ email của người dùng (duy nhất)"
        string uid "Firebase user ID (duy nhất)"
        string payment_provider_customer_id "Stripe customer id bắt đầu với 'cus_' (có thể null)"
        integer status "Trạng thái tài khoản: 0: Không hoạt động, 1: Hoạt động"
        integer is_first_login "1: đã đăng nhập, 0: chưa đăng nhập"
        string remember_token "Sử dụng cho chức năng 'remember me' (có thể null)"
    }
    password_resets {
        string email "Địa chỉ email của người dùng (index)"
        string token "Reset token (đã hash)"
        timestamp expires_at "Thời gian hết hạn token"
    }

    users ||--o{ password_resets : yêu cầu

Xử lý lỗi

  • Ghi log

    • Lỗi gửi email được ghi log
    • Vấn đề tạo token được ghi lại
    • (Tùy chọn) Gửi slack message cho các sự kiện bảo mật
  • Chi tiết lỗi:

    Mã trạng thái Thông báo lỗi Mô tả
    400 "メールアドレスが見つかりません。" Khi email không được tìm thấy
    400 "メールの送信に失敗しました。" Khi gửi email thất bại
    429 "リクエストが多すぎます。" Khi vượt quá giới hạn tốc độ
    400 Generic error với exception message Khi xảy ra lỗi không mong muốn

Trường hợp 2: Đặt lại mật khẩu với Token

Mô tả

Người dùng đặt lại mật khẩu của họ sử dụng token nhận được qua email.

Sơ đồ tuần tự

sequenceDiagram
    participant User
    participant API as ResetPasswordController
    participant Service as AuthService
    participant TokenDB as Database
    participant Firebase

    User->>API: POST /api/v1/general/auth/reset
    API->>Service: resetPassword(token, email, password)
    Service->>TokenDB: validateToken(token, email)
    TokenDB-->>Service: Kết quả xác minh token
    Service->>Firebase: updatePassword(email, password)
    Firebase-->>Service: Trạng thái cập nhật mật khẩu
    Service->>TokenDB: deleteToken(token, email)
    Service-->>API: Kết quả đặt lại
    API-->>User: 200 OK với thông báo thành công

Các bước

Bước 1: Gửi form đặt lại

  • Mô tả: Người dùng gửi mật khẩu mới với reset token
  • Request: POST /api/v1/general/auth/reset
  • Body Parameters:
    • token: Reset token từ email
    • email: Địa chỉ email của người dùng
    • password: Mật khẩu mới
    • password_confirmation: Xác nhận mật khẩu
  • Validation:
    • Sự hiện diện và định dạng token
    • Độ mạnh mật khẩu và xác nhận
    • Email khớp với token
    • Kiểm tra giới hạn tốc độ (3 lần thử mỗi giờ)

Bước 2: Xác minh Token

  • Mô tả: Xác minh tính hợp lệ và thời gian hết hạn của token
  • Hành động:
    • Kiểm tra token tồn tại trong database
    • Xác minh token chưa hết hạn
    • Xác nhận token khớp với email được cung cấp
    • Ghi log lần thử xác minh

Bước 3: Cập nhật mật khẩu

  • Mô tả: Đặt mật khẩu mới cho user
  • Hành động:
    • Cập nhật mật khẩu trong Firebase authentication
    • Áp dụng hash mật khẩu theo tiêu chuẩn bảo mật
    • Ghi log cập nhật mật khẩu

Bước 4: Xóa Token

  • Mô tả: Loại bỏ token đã sử dụng khỏi database
  • Hành động:
    • Xóa bản ghi token để ngăn tái sử dụng
    • Dọn dẹp bất kỳ token hết hạn nào
    • Ghi log xóa token

Bước 5: Trả về Response

  • Mô tả: Thông báo cho user về thành công đặt lại mật khẩu
  • Response:
    • Thành công: 200 OK với thông báo thành công
    • Lỗi: Mã lỗi phù hợp với thông báo chi tiết

Xử lý lỗi

  • Ghi log

    • Lỗi xác minh token
    • Lỗi cập nhật mật khẩu
    • (Tùy chọn) Gửi slack message cho các sự kiện bảo mật
  • Chi tiết lỗi:

    Mã trạng thái Thông báo lỗi Mô tả
    400 "無効なトークンです。" Khi token không hợp lệ hoặc hết hạn
    400 "パスワードリセットに失敗しました。" Khi đặt lại mật khẩu thất bại
    429 "リクエストが多すぎます。" Khi vượt quá giới hạn tốc độ
    400 Generic error với exception message Khi xảy ra lỗi không mong muốn

Ghi chú bổ sung

  • Giới hạn tốc độ: Các lần thử đặt lại bị giới hạn 3 lần mỗi giờ cho mỗi địa chỉ IP
  • Reset tokens hết hạn sau 60 phút
  • Hệ thống ngăn tái sử dụng token bằng cách xóa tokens sau khi đặt lại mật khẩu thành công
  • Yêu cầu mật khẩu:
    • Tối thiểu 8 ký tự
    • Ít nhất một chữ cái viết hoa
    • Ít nhất một chữ cái viết thường
    • Ít nhất một số
    • Ít nhất một ký tự đặc biệt
  • Reset URL được cung cấp bởi client application để cho phép các triển khai front-end khác nhau
  • Xem xét triển khai các biện pháp bảo mật bổ sung:
    • Giới hạn tốc độ dựa trên IP
    • Khóa tài khoản sau nhiều lần thử thất bại
    • Thông báo email khi thay đổi mật khẩu thành công