Skip to content

feat: password reset code for users to use in the CLI#8

Open
raz-iacob wants to merge 4 commits intosupo-dev:mainfrom
raz-iacob:feat/reset-password
Open

feat: password reset code for users to use in the CLI#8
raz-iacob wants to merge 4 commits intosupo-dev:mainfrom
raz-iacob:feat/reset-password

Conversation

@raz-iacob
Copy link
Contributor

@raz-iacob raz-iacob commented Oct 3, 2025

Added password reset functionality for users using the terminal/CLI

What's new

  • POST /password/email - Send reset code via notification
  • PUT /password/reset - Reset password with token / code
  • Uses the default Laravel token system
  • Follows existing controller patterns (actions, requests, proper responses)
  • All tests included

Summary by CodeRabbit

  • New Features

    • Added a password reset flow with two public API endpoints to request a reset code and submit a new password using a token.
    • Sends queued email notifications containing a reset token and instructions.
    • Adds server-side validation for email, token, password confirmation, and minimum password length; returns clear success or error responses.
  • Tests

    • Added end-to-end and unit tests covering requesting reset codes, successful resets, invalid tokens, and password validation errors.
  • Documentation

    • Added README section documenting the password reset endpoints and usage.

@coderabbitai
Copy link

coderabbitai bot commented Oct 3, 2025

Walkthrough

Adds a password-reset flow: actions to send a reset token and perform a reset, a controller with store/update endpoints, validation requests, a queued mail notification carrying the token, API routes, and feature/unit tests covering success and error cases.

Changes

Cohort / File(s) Summary
Actions
app/Actions/SendPasswordResetCode.php, app/Actions/ResetPassword.php
New final readonly action classes. SendPasswordResetCode finds a user, creates a password token via Password::createToken($user), and notifies the user. ResetPassword calls Password::reset, hashes and saves the new password, updates the remember token, and returns the reset status string.
Controller
app/Http/Controllers/PasswordResetController.php
New controller with store (POST /password/email) and update (PUT /password/reset) methods; invokes actions and returns HTTP/JSON responses based on Password facade results.
Requests
app/Http/Requests/SendPasswordResetCodeRequest.php, app/Http/Requests/ResetPasswordRequest.php
New FormRequest validators: SendPasswordResetCodeRequest requires email (bail, required, email); ResetPasswordRequest requires token (required,string), email (required,email,exists:users), and password (required,confirmed,min:8).
Notification
app/Notifications/PasswordResetNotification.php
New queueable mail notification that stores a token, delivers via mail, uses a greeting with the notifiable's username, and exposes the token via toArray.
Routes
routes/api.php
Registers POST /password/emailPasswordResetController::store (name: password.email) and PUT /password/resetPasswordResetController::update (name: password.reset).
Feature Tests
tests/Feature/Http/PasswordResetControllerTest.php
End-to-end tests: request reset link, reset with valid token, invalid token returns 400, password mismatch returns 422; uses Notification::fake().
Unit Tests
tests/Unit/Actions/SendPasswordResetCodeTest.php, tests/Unit/Actions/ResetPasswordTest.php
Unit tests for actions: SendPasswordResetCode asserts notification sent and token shape; ResetPassword asserts action returns Password::PASSWORD_RESET (mocks Password::reset).
Docs
README.md
Adds "Password Reset" documentation section listing POST /password/email and PUT /password/reset endpoints.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant API as API (routes)
  participant C as PasswordResetController
  participant A1 as SendPasswordResetCode
  participant A2 as ResetPassword
  participant P as Password Facade
  participant N as PasswordResetNotification
  participant U as User Model

  rect rgb(240,248,255)
    User->>API: POST /password/email { email }
    API->>C: store(request)
    C->>A1: handle(email)
    A1->>U: find by email
    alt found
      A1->>P: createToken(user)
      A1->>N: new PasswordResetNotification(token)
      A1-->>U: notify(N)
    else not found
      A1-->>C: return
    end
    C-->>User: 200 OK
  end

  rect rgb(245,255,240)
    User->>API: PUT /password/reset { token,email,password,password_confirmation }
    API->>C: update(request)
    C->>A2: handle(token,email,password,password_confirmation)
    A2->>P: reset(credentials, callback)
    alt success
      P-->>A2: PASSWORD_RESET
      A2-->>C: PASSWORD_RESET
      C-->>User: 200 JSON { message }
    else invalid
      P-->>A2: INVALID_TOKEN
      A2-->>C: INVALID_TOKEN
      C-->>User: 400 JSON { message }
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I thump my feet—reset complete!
A token blooms, a secret treat.
Two hops to mail, one hop to set,
New keys forged—no need to fret.
In burrows safe, your creds now stay. 🐇🔐

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit's high-level summary is enabled.
Title Check ✅ Passed The title clearly indicates the addition of password reset code functionality, reflecting the new API endpoints and notification flow intended for CLI use.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59daaaf and e412bc7.

📒 Files selected for processing (1)
  • README.md (1 hunks)
🔇 Additional comments (1)
README.md (1)

34-37: Docs update looks accurate.

Endpoints match the new password reset flow and keep the README consistent.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b5b970 and af95269.

📒 Files selected for processing (10)
  • app/Actions/ResetPassword.php (1 hunks)
  • app/Actions/SendPasswordResetCode.php (1 hunks)
  • app/Http/Controllers/PasswordResetController.php (1 hunks)
  • app/Http/Requests/ResetPasswordRequest.php (1 hunks)
  • app/Http/Requests/SendPasswordResetCodeRequest.php (1 hunks)
  • app/Notifications/PasswordResetNotification.php (1 hunks)
  • routes/api.php (2 hunks)
  • tests/Feature/Http/PasswordResetControllerTest.php (1 hunks)
  • tests/Unit/Actions/ResetPasswordTest.php (1 hunks)
  • tests/Unit/Actions/SendPasswordResetCodeTest.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (10)
routes/api.php (1)
app/Http/Controllers/PasswordResetController.php (1)
  • PasswordResetController (15-47)
app/Http/Requests/ResetPasswordRequest.php (1)
app/Http/Requests/SendPasswordResetCodeRequest.php (1)
  • rules (14-19)
app/Notifications/PasswordResetNotification.php (1)
app/Models/User.php (1)
  • User (27-82)
app/Actions/SendPasswordResetCode.php (3)
app/Models/User.php (1)
  • User (27-82)
app/Notifications/PasswordResetNotification.php (1)
  • PasswordResetNotification (13-51)
app/Actions/ResetPassword.php (1)
  • handle (14-33)
app/Actions/ResetPassword.php (2)
app/Models/User.php (1)
  • User (27-82)
app/Actions/SendPasswordResetCode.php (1)
  • handle (13-22)
tests/Feature/Http/PasswordResetControllerTest.php (2)
app/Models/User.php (1)
  • User (27-82)
app/Notifications/PasswordResetNotification.php (2)
  • PasswordResetNotification (13-51)
  • toArray (45-50)
app/Http/Requests/SendPasswordResetCodeRequest.php (1)
app/Http/Requests/ResetPasswordRequest.php (1)
  • rules (14-21)
tests/Unit/Actions/ResetPasswordTest.php (2)
app/Actions/ResetPassword.php (2)
  • ResetPassword (12-34)
  • handle (14-33)
app/Models/User.php (1)
  • User (27-82)
tests/Unit/Actions/SendPasswordResetCodeTest.php (3)
app/Actions/SendPasswordResetCode.php (2)
  • SendPasswordResetCode (11-23)
  • handle (13-22)
app/Models/User.php (1)
  • User (27-82)
app/Notifications/PasswordResetNotification.php (3)
  • PasswordResetNotification (13-51)
  • toArray (45-50)
  • toMail (31-40)
app/Http/Controllers/PasswordResetController.php (4)
app/Actions/ResetPassword.php (2)
  • ResetPassword (12-34)
  • handle (14-33)
app/Actions/SendPasswordResetCode.php (2)
  • SendPasswordResetCode (11-23)
  • handle (13-22)
app/Http/Requests/ResetPasswordRequest.php (1)
  • ResetPasswordRequest (9-22)
app/Http/Requests/SendPasswordResetCodeRequest.php (1)
  • SendPasswordResetCodeRequest (9-20)
🪛 PHPMD (2.15.0)
app/Notifications/PasswordResetNotification.php

26-26: Avoid unused parameters such as '$notifiable'. (undefined)

(UnusedFormalParameter)


45-45: Avoid unused parameters such as '$notifiable'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (2)
tests/Feature/Http/PasswordResetControllerTest.php (1)

13-77: Solid end-to-end coverage.

These scenarios give us high confidence in the new flow—nice work keeping the happy path and the key failure modes under test.

app/Http/Controllers/PasswordResetController.php (1)

17-46: Controller flow looks clean.

The request casting, action delegation, and status handling are straightforward and match the expectations laid out in the tests.

Comment on lines +17 to +19
'token' => ['required', 'string'],
'email' => ['required', 'email', 'exists:users'],
'password' => ['required', 'confirmed', 'min:8'],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove email existence check to prevent enumeration.

Validating with exists:users here also leaks whether an account exists by returning a 422 before the broker runs. Let the broker handle unknown emails (it already maps failures to an opaque message), and keep the response indistinguishable from the valid flow.

-            'email' => ['required', 'email', 'exists:users'],
+            'email' => ['required', 'email'],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'token' => ['required', 'string'],
'email' => ['required', 'email', 'exists:users'],
'password' => ['required', 'confirmed', 'min:8'],
'token' => ['required', 'string'],
'email' => ['required', 'email'],
'password' => ['required', 'confirmed', 'min:8'],
🤖 Prompt for AI Agents
In app/Http/Requests/ResetPasswordRequest.php around lines 17 to 19, the
validation currently includes an 'exists:users' rule for the email which leaks
account existence; remove the 'exists:users' rule from the email validation
array so only format is validated (e.g., keep 'required' and 'email'), letting
the password broker handle unknown emails and preserve an indistinguishable
response.

Comment on lines +26 to +29
public function via(User $notifiable): array
{
return ['mail'];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix the via signature to match the base class.

Notification::via() declares object $notifiable; narrowing it to User causes a fatal “Declaration ... must be compatible” error. Keep the parameter typed as object (or untyped) and add a docblock if you need stronger hints.

-    public function via(User $notifiable): array
+    public function via(object $notifiable): array
     {
         return ['mail'];
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function via(User $notifiable): array
{
return ['mail'];
}
public function via(object $notifiable): array
{
return ['mail'];
}
🧰 Tools
🪛 PHPMD (2.15.0)

26-26: Avoid unused parameters such as '$notifiable'. (undefined)

(UnusedFormalParameter)

🤖 Prompt for AI Agents
In app/Notifications/PasswordResetNotification.php around lines 26 to 29, the
method signature public function via(User $notifiable): array narrows the
parameter type and is incompatible with the base Notification::via(object
$notifiable); change the signature to accept object (e.g. public function
via(object $notifiable): array) and, if you need stronger IDE/type hints, add a
docblock like @param User|object $notifiable above the method to document the
expected User type.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
app/Http/Requests/SendPasswordResetCodeRequest.php (1)

14-19: Consider consistent email validation across password reset endpoints.

While this request correctly omits exists:users to prevent user enumeration, the companion ResetPasswordRequest (lines 13-20 in app/Http/Requests/ResetPasswordRequest.php) still includes exists:users on the email field. This creates an inconsistency where the reset endpoint could leak whether an email is registered, even though the request-code endpoint does not.

For consistency and security, consider removing exists:users from ResetPasswordRequest as well, allowing the action layer to handle missing users gracefully.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af95269 and 59daaaf.

📒 Files selected for processing (1)
  • app/Http/Requests/SendPasswordResetCodeRequest.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/Http/Requests/SendPasswordResetCodeRequest.php (1)
app/Http/Requests/ResetPasswordRequest.php (1)
  • rules (14-21)
🔇 Additional comments (1)
app/Http/Requests/SendPasswordResetCodeRequest.php (1)

17-17: LGTM! Security guidance correctly applied.

The validation rules correctly omit exists:users, preventing account enumeration attacks. This matches the guidance from the previous review and ensures that the endpoint returns the same response regardless of whether the email is registered.

@MrPunyapal
Copy link
Collaborator

You need to remove http layer first than need to think password reset again with new flow.

@raz-iacob
Copy link
Contributor Author

yeah.. with the new change I'll do that. and I'll use cache for the password reset code

@raz-iacob
Copy link
Contributor Author

I noticed that the email field was removed from the user. Without an email, there’s no way to reset a password if someone forgets it.

I think a better user experience would be to use an OTP (one-time password) flow. Instead of asking users to create a password, they could just provide an email, and we’d send them a one-time password to log in.

If they later access Supo with the same SSH key, we can skip the sign-in step entirely.

Does that make sense, @nunomaduro, @MrPunyapal ?

@MrPunyapal
Copy link
Collaborator

I think we will need email and email verification both to check if user is authentic (not spam).

cc: @nunomaduro

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants