customer-registration 0.0.112 → 0.0.113
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/api/auth/customer/emailpass/reset-password/route.js +1 -26
- package/.medusa/server/src/api/auth/customer/emailpass/route.js +19 -97
- package/.medusa/server/src/api/auth/customer/phonepass/register/route.js +50 -0
- package/.medusa/server/src/api/auth/customer/phonepass/route.js +105 -0
- package/.medusa/server/src/api/middlewares/{block-pending-customer.js → guard-account-deletion.js} +4 -4
- package/.medusa/server/src/api/middlewares/ip-rate-limit.js +48 -0
- package/.medusa/server/src/api/middlewares/validate-customer-registration.js +60 -0
- package/.medusa/server/src/api/middlewares.js +17 -4
- package/.medusa/server/src/api/store/customers/account-deletion/cancel-request/route.js +16 -6
- package/.medusa/server/src/api/store/customers/account-deletion/validators.js +11 -3
- package/.medusa/server/src/api/store/customers/me/contact/route.js +95 -0
- package/.medusa/server/src/api/store/customers/me/contact/verify/route.js +83 -0
- package/.medusa/server/src/api/store/customers/me/route.js +53 -0
- package/.medusa/server/src/api/store/customers/otp/send/route.js +1 -6
- package/.medusa/server/src/api/store/customers/otp/verify/route.js +95 -3
- package/.medusa/server/src/api/store/customers/route.js +89 -0
- package/.medusa/server/src/config.js +32 -23
- package/.medusa/server/src/modules/otp-verification/service.js +72 -1
- package/.medusa/server/src/providers/phonepass/index.js +9 -0
- package/.medusa/server/src/providers/phonepass/service.js +133 -0
- package/.medusa/server/src/subscribers/password-reset.js +1 -42
- package/.medusa/server/src/workflows/change-password.js +40 -64
- package/.medusa/server/src/workflows/send-contact-change-otp-workflow.js +41 -0
- package/.medusa/server/src/workflows/steps/determine-contact-method-step.js +8 -2
- package/.medusa/server/src/workflows/steps/generate-contact-change-otp-step.js +24 -0
- package/.medusa/server/src/workflows/steps/index.js +6 -2
- package/.medusa/server/src/workflows/steps/send-notification-step.js +1 -11
- package/.medusa/server/src/workflows/steps/sync-phonepass-entity-id-step.js +63 -0
- package/.medusa/server/src/workflows/steps/update-password-step.js +21 -29
- package/.medusa/server/src/workflows/update-contact-workflow.js +100 -0
- package/.medusa/server/src/workflows/verify-phone.js +11 -4
- package/README.md +363 -223
- package/package.json +3 -1
- package/.medusa/server/src/subscribers/customer-updated.js +0 -100
package/README.md
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
# Medusa Plugin: Customer Registration & OTP Verification
|
|
2
2
|
|
|
3
|
-
A comprehensive Medusa v2 plugin that provides OTP-based verification for email and phone functionality, with support for
|
|
3
|
+
A comprehensive Medusa v2 plugin that provides OTP-based verification for email and phone functionality, with support for flexible registration identifiers, phone-only authentication, contact change flows, and account deletion.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
7
|
+
- **Unified OTP API**: Single endpoints for sending and verifying OTPs
|
|
8
|
+
- **Token-based Verification**: Secure JWT token system for OTP verification
|
|
9
|
+
- **Multiple Verification Types**: Email verification and phone verification
|
|
10
|
+
- **Workflow-based Processing**: Automatic handling of verification flags
|
|
11
|
+
- **Password Reset Support**: Notification subscriber for Medusa's built-in password reset flow
|
|
12
|
+
- **Flexible Configuration**: Per-purpose channel configuration (email/SMS)
|
|
13
|
+
- **Throttling & Rate Limiting**: Built-in protection against OTP spam
|
|
14
|
+
- **Database Migrations**: Automatic schema updates for verification columns
|
|
15
|
+
- **Account Deletion Request Flow**: Two-step OTP flow to request and confirm account deletion with optional cancel flow
|
|
16
|
+
- **Phone-only Authentication**: `phonepass` auth provider for registering and logging in with phone + password (no email required)
|
|
17
|
+
- **Authenticated Contact Change**: Dedicated OTP-verified routes for updating phone or email — new value is embedded in the signed JWT, no metadata staging required
|
|
17
18
|
|
|
18
19
|
## Quick Start
|
|
19
20
|
|
|
@@ -31,16 +32,21 @@ export default defineConfig({
|
|
|
31
32
|
{
|
|
32
33
|
resolve: "customer-registration",
|
|
33
34
|
options: {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
storefrontUrl: "http://localhost:8000",
|
|
36
|
+
|
|
37
|
+
registration: {
|
|
38
|
+
identifier: "phone", // "email" | "phone" | "both"
|
|
39
|
+
require_verification: true,
|
|
40
|
+
},
|
|
41
|
+
login: {
|
|
42
|
+
identifier: "phone", // defaults to registration.identifier
|
|
43
|
+
},
|
|
44
|
+
|
|
38
45
|
password_reset: {
|
|
39
|
-
template: "src/templates/emails/password-reset.html",
|
|
40
|
-
subject: "Reset Your Password",
|
|
46
|
+
template: "src/templates/emails/password-reset.html",
|
|
47
|
+
subject: "Reset Your Password",
|
|
41
48
|
},
|
|
42
49
|
|
|
43
|
-
// Account deletion OTP emails (optional; required if using account deletion flow)
|
|
44
50
|
account_deletion_request: {
|
|
45
51
|
template: "src/templates/emails/account-deletion-request.html",
|
|
46
52
|
subject: "Confirm your account deletion request",
|
|
@@ -50,8 +56,7 @@ export default defineConfig({
|
|
|
50
56
|
template: "src/templates/emails/account-deletion-cancel.html",
|
|
51
57
|
subject: "Confirm cancellation of account deletion",
|
|
52
58
|
},
|
|
53
|
-
|
|
54
|
-
// OTP channel configuration
|
|
59
|
+
|
|
55
60
|
email_verification: {
|
|
56
61
|
channel: "email",
|
|
57
62
|
subject: "Verify your email",
|
|
@@ -65,33 +70,17 @@ export default defineConfig({
|
|
|
65
70
|
})
|
|
66
71
|
```
|
|
67
72
|
|
|
68
|
-
**Required Options:**
|
|
69
|
-
- `password_reset.template` - Path to HTML template file for password reset emails
|
|
70
|
-
|
|
71
73
|
3. **Run migrations**:
|
|
72
74
|
```bash
|
|
73
75
|
npx medusa db:migrate
|
|
74
76
|
```
|
|
75
77
|
|
|
76
|
-
4. **Use the API
|
|
77
|
-
```bash
|
|
78
|
-
# Send OTP
|
|
79
|
-
POST /store/customers/otp/send
|
|
80
|
-
{
|
|
81
|
-
"customer_id": "cus_...",
|
|
82
|
-
"type": "email_verification"
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
# Verify OTP
|
|
86
|
-
POST /store/customers/otp/verify
|
|
87
|
-
{
|
|
88
|
-
"token": "...",
|
|
89
|
-
"code": "123456"
|
|
90
|
-
}
|
|
91
|
-
```
|
|
78
|
+
4. **Use the API** — see [API Endpoints](#api-endpoints) below.
|
|
92
79
|
|
|
93
80
|
📖 **For complete documentation, see [USAGE.md](./USAGE.md)**
|
|
94
81
|
|
|
82
|
+
---
|
|
83
|
+
|
|
95
84
|
## Installation
|
|
96
85
|
|
|
97
86
|
### Local Development
|
|
@@ -108,18 +97,7 @@ cd ../../test-medusa
|
|
|
108
97
|
npx medusa plugin:add customer-registration
|
|
109
98
|
```
|
|
110
99
|
|
|
111
|
-
3. Register the plugin in `medusa-config.ts
|
|
112
|
-
```typescript
|
|
113
|
-
module.exports = defineConfig({
|
|
114
|
-
// ... other config
|
|
115
|
-
plugins: [
|
|
116
|
-
{
|
|
117
|
-
resolve: "customer-registration",
|
|
118
|
-
options: {},
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
})
|
|
122
|
-
```
|
|
100
|
+
3. Register the plugin in `medusa-config.ts` (see Quick Start above).
|
|
123
101
|
|
|
124
102
|
4. Start development mode (in plugin directory):
|
|
125
103
|
```bash
|
|
@@ -133,269 +111,431 @@ cd ../../test-medusa
|
|
|
133
111
|
yarn dev
|
|
134
112
|
```
|
|
135
113
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
### Registration lifecycle hook
|
|
114
|
+
---
|
|
139
115
|
|
|
140
|
-
|
|
116
|
+
## Configuration
|
|
141
117
|
|
|
142
|
-
###
|
|
143
|
-
|
|
144
|
-
The plugin provides unified OTP endpoints:
|
|
145
|
-
|
|
146
|
-
| Endpoint | Method | Description |
|
|
147
|
-
| --- | --- | --- |
|
|
148
|
-
| `/store/customers/otp/send` | POST | Send OTP for email/phone verification |
|
|
149
|
-
| `/store/customers/otp/verify` | POST | Verify OTP code and execute appropriate workflow |
|
|
118
|
+
### `registration.identifier`
|
|
150
119
|
|
|
151
|
-
|
|
152
|
-
- `email_verification` - Verify customer email
|
|
153
|
-
- `phone_verification` - Verify customer phone
|
|
154
|
-
- Note: Password reset uses Medusa's built-in flow (see Password Reset section below)
|
|
120
|
+
Controls which field is required at sign-up:
|
|
155
121
|
|
|
156
|
-
|
|
122
|
+
| Value | Behaviour |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `"email"` (default) | Email required; standard Medusa registration |
|
|
125
|
+
| `"phone"` | Phone required; no email needed; uses `phonepass` provider |
|
|
126
|
+
| `"both"` | Both email and phone required |
|
|
157
127
|
|
|
158
|
-
###
|
|
128
|
+
### `login.identifier`
|
|
159
129
|
|
|
160
|
-
|
|
161
|
-
2. **Send OTP** - Request OTP using unified endpoint with type
|
|
162
|
-
3. **Verify OTP** - Verify code using token from send response
|
|
163
|
-
4. **Login** - Customer can login after email verification
|
|
130
|
+
Controls which auth providers are accepted at login. Defaults to `registration.identifier`.
|
|
164
131
|
|
|
165
|
-
|
|
132
|
+
| Value | Accepted login methods |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `"email"` | `POST /auth/customer/emailpass` only |
|
|
135
|
+
| `"phone"` | `POST /auth/customer/phonepass` only |
|
|
136
|
+
| `"both"` | Both emailpass and phonepass accepted |
|
|
166
137
|
|
|
167
|
-
###
|
|
168
|
-
|
|
169
|
-
The plugin uses purpose-based configuration:
|
|
138
|
+
### Full options reference
|
|
170
139
|
|
|
171
140
|
```typescript
|
|
172
141
|
{
|
|
173
142
|
resolve: "customer-registration",
|
|
174
143
|
options: {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
144
|
+
storefrontUrl?: string, // Base URL for password reset links
|
|
145
|
+
|
|
146
|
+
registration?: {
|
|
147
|
+
identifier?: "email" | "phone" | "both", // default: "email"
|
|
148
|
+
require_verification?: boolean, // default: true
|
|
149
|
+
},
|
|
150
|
+
login?: {
|
|
151
|
+
identifier?: "email" | "phone" | "both", // default: registration.identifier
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// OTP settings (apply to all types)
|
|
155
|
+
otpLength?: number, // default: 6
|
|
156
|
+
otpCharset?: "numeric" | "alphanumeric", // default: "numeric"
|
|
157
|
+
otpExpiryMinutes?: number, // default: 15
|
|
158
|
+
maxAttempts?: number, // default: 5
|
|
159
|
+
|
|
160
|
+
email_verification?: {
|
|
161
|
+
channel: string, // e.g. "email"
|
|
162
|
+
template?: string,
|
|
163
|
+
subject?: string,
|
|
164
|
+
resendThrottleSeconds?: number,
|
|
165
|
+
autoSendOnRegistration?: boolean,
|
|
184
166
|
},
|
|
185
|
-
phone_verification
|
|
186
|
-
channel: "sms"
|
|
187
|
-
template
|
|
188
|
-
resendThrottleSeconds
|
|
167
|
+
phone_verification?: {
|
|
168
|
+
channel: string, // e.g. "sms"
|
|
169
|
+
template?: string,
|
|
170
|
+
resendThrottleSeconds?: number,
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
password_reset?: {
|
|
174
|
+
template: string, // Required when using password reset
|
|
175
|
+
subject?: string,
|
|
176
|
+
},
|
|
177
|
+
account_deletion_request?: {
|
|
178
|
+
template: string,
|
|
179
|
+
subject?: string,
|
|
180
|
+
scheduled_days?: number, // default: 7
|
|
181
|
+
},
|
|
182
|
+
account_deletion_cancel?: {
|
|
183
|
+
template: string,
|
|
184
|
+
subject?: string,
|
|
189
185
|
},
|
|
190
186
|
},
|
|
191
187
|
}
|
|
192
188
|
```
|
|
193
189
|
|
|
194
|
-
|
|
190
|
+
---
|
|
195
191
|
|
|
196
|
-
|
|
192
|
+
## API Endpoints
|
|
197
193
|
|
|
198
|
-
|
|
194
|
+
### Registration & Initial Verification
|
|
199
195
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
196
|
+
| Endpoint | Method | Auth | Description |
|
|
197
|
+
|---|---|---|---|
|
|
198
|
+
| `/store/customers` | POST | Bearer (pre-customer) | Create customer account |
|
|
199
|
+
| `/store/customers/otp/send` | POST | — | Send OTP for initial email/phone verification |
|
|
200
|
+
| `/store/customers/otp/verify` | POST | — | Verify OTP code; returns login token on success |
|
|
203
201
|
|
|
204
|
-
|
|
205
|
-
- Creates `otp_verification` table for storing OTP records
|
|
202
|
+
#### Send OTP
|
|
206
203
|
|
|
207
|
-
|
|
208
|
-
|
|
204
|
+
```bash
|
|
205
|
+
POST /store/customers/otp/send
|
|
206
|
+
{
|
|
207
|
+
"customer_id": "cus_...",
|
|
208
|
+
"type": "phone_verification" # or "email_verification"
|
|
209
|
+
}
|
|
210
|
+
# Response: { "token": "...", "expires_at": "..." }
|
|
211
|
+
```
|
|
209
212
|
|
|
210
|
-
|
|
211
|
-
- Extends `otp_verification.purpose` to include `account_deletion_request` and `account_deletion_cancel`
|
|
213
|
+
#### Verify OTP
|
|
212
214
|
|
|
213
|
-
Run migrations after installation:
|
|
214
215
|
```bash
|
|
215
|
-
|
|
216
|
+
POST /store/customers/otp/verify
|
|
217
|
+
{
|
|
218
|
+
"token": "<token from send>",
|
|
219
|
+
"code": "482917"
|
|
220
|
+
}
|
|
221
|
+
# Response: { "verified": true, "customer": {...}, "token": "<login jwt>", "needs_login": false }
|
|
216
222
|
```
|
|
217
223
|
|
|
218
|
-
|
|
224
|
+
---
|
|
219
225
|
|
|
220
|
-
|
|
221
|
-
- Node.js >= 20
|
|
222
|
-
- Notification module configured with at least one provider (email/SMS)
|
|
223
|
-
- Database migrations applied (`npx medusa db:migrate`)
|
|
226
|
+
### Login
|
|
224
227
|
|
|
225
|
-
|
|
228
|
+
| Endpoint | Method | Description |
|
|
229
|
+
|---|---|---|
|
|
230
|
+
| `/auth/customer/emailpass` | POST | Login with email + password |
|
|
231
|
+
| `/auth/customer/phonepass` | POST | Login with phone + password |
|
|
232
|
+
| `/auth/customer/emailpass/register` | POST | Create emailpass auth identity |
|
|
233
|
+
| `/auth/customer/phonepass/register` | POST | Create phonepass auth identity |
|
|
226
234
|
|
|
227
|
-
|
|
228
|
-
- **[README.md](./README.md)** - This file (overview and quick start)
|
|
235
|
+
---
|
|
229
236
|
|
|
230
|
-
|
|
237
|
+
### Contact Change (Phone or Email Update)
|
|
231
238
|
|
|
232
|
-
|
|
239
|
+
> These are the dedicated authenticated routes for changing a customer's phone or email.
|
|
240
|
+
> `PATCH /store/customers/me` **rejects** `phone` and `email` fields — all contact changes must go through these endpoints.
|
|
233
241
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
242
|
+
| Endpoint | Method | Auth | Description |
|
|
243
|
+
|---|---|---|---|
|
|
244
|
+
| `/store/customers/me/contact` | POST | Customer JWT | Request contact change — validates new value, sends OTP, returns token |
|
|
245
|
+
| `/store/customers/me/contact/verify` | POST | Customer JWT | Verify OTP and apply the change |
|
|
237
246
|
|
|
238
|
-
|
|
247
|
+
#### How it works
|
|
239
248
|
|
|
240
|
-
The
|
|
249
|
+
The new phone or email is embedded directly in the signed OTP JWT. No metadata is written to the customer record during the pending state. On successful verification:
|
|
241
250
|
|
|
242
|
-
|
|
243
|
-
|
|
251
|
+
1. `customer.phone` / `customer.email` is updated
|
|
252
|
+
2. `phone_verified` / `email_verified` is set to `true`
|
|
253
|
+
3. `provider_identity.entity_id` is updated for `phonepass` / `emailpass` **only when the changed field is the active `login.identifier`** — so login continues to work with the new value
|
|
244
254
|
|
|
245
|
-
|
|
255
|
+
#### Step 1 — Request the change
|
|
246
256
|
|
|
247
|
-
|
|
257
|
+
```bash
|
|
258
|
+
curl -X POST http://localhost:9000/store/customers/me/contact \
|
|
259
|
+
-H "Content-Type: application/json" \
|
|
260
|
+
-H "Authorization: Bearer $CUSTOMER_JWT" \
|
|
261
|
+
-d '{ "phone": "+15559998888" }'
|
|
248
262
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
- Returns 201 status (matches Medusa's built-in route behavior)
|
|
253
|
-
2. **Email Sent**: Password reset email is sent with reset token and URL
|
|
254
|
-
3. **Complete Reset**: POST to `/auth/customer/emailpass/update` with token and new password
|
|
255
|
-
- Uses AUTH module's `authenticate()` method with `update-password` action
|
|
256
|
-
- AUTH module validates token and updates password
|
|
263
|
+
# Response
|
|
264
|
+
{ "token": "<otp_token>", "expires_at": "2026-03-12T10:15:00.000Z" }
|
|
265
|
+
```
|
|
257
266
|
|
|
258
|
-
|
|
259
|
-
- Uses Medusa's AUTH module for token generation and password updates
|
|
260
|
-
- Custom routes that match Medusa's built-in route behavior
|
|
261
|
-
- Email sending handled directly in the route (subscriber available for `auth.password_reset` event)
|
|
262
|
-
- Consistent with Medusa's authentication patterns
|
|
267
|
+
Only one field at a time is accepted. Sending both `phone` and `email` returns a `400`.
|
|
263
268
|
|
|
264
|
-
|
|
269
|
+
#### Step 2 — Verify the OTP
|
|
265
270
|
|
|
266
|
-
|
|
267
|
-
-
|
|
268
|
-
-
|
|
269
|
-
-
|
|
271
|
+
```bash
|
|
272
|
+
curl -X POST http://localhost:9000/store/customers/me/contact/verify \
|
|
273
|
+
-H "Content-Type: application/json" \
|
|
274
|
+
-H "Authorization: Bearer $CUSTOMER_JWT" \
|
|
275
|
+
-d '{
|
|
276
|
+
"token": "<otp_token from step 1>",
|
|
277
|
+
"code": "482917"
|
|
278
|
+
}'
|
|
270
279
|
|
|
271
|
-
|
|
280
|
+
# Response
|
|
281
|
+
{
|
|
282
|
+
"customer": { "id": "cus_...", "phone": "+15559998888", ... },
|
|
283
|
+
"token": "<fresh_login_jwt>"
|
|
284
|
+
}
|
|
285
|
+
```
|
|
272
286
|
|
|
273
|
-
The
|
|
287
|
+
The response includes a **fresh login JWT** so the caller is immediately re-authenticated after the change.
|
|
274
288
|
|
|
275
|
-
|
|
276
|
-
import {
|
|
277
|
-
requestPasswordReset,
|
|
278
|
-
completePasswordReset,
|
|
279
|
-
} from "customer-registration/helpers"
|
|
289
|
+
#### Security notes
|
|
280
290
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
)
|
|
291
|
+
- The OTP token is signed with the server's `jwtSecret` — the new value cannot be tampered with
|
|
292
|
+
- The verify route checks that `token.customer_id === auth_context.actor_id`, preventing one customer from applying another's OTP
|
|
293
|
+
- `provider_identity.entity_id` sync has a compensation function: if a later workflow step fails the `entity_id` is rolled back to the original value
|
|
294
|
+
- If the current phone/email is `null` (first-time contact assignment), the flow works identically — the field is set from null to the new value and marked verified
|
|
286
295
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
+
#### What `PATCH /store/customers/me` does now
|
|
297
|
+
|
|
298
|
+
Non-contact fields (e.g. `first_name`, `last_name`) continue to work as before. Passing `phone` or `email` in the body returns:
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"type": "not_allowed",
|
|
303
|
+
"message": "Phone changes require OTP verification. Use POST /store/customers/me/contact to request a change."
|
|
304
|
+
}
|
|
296
305
|
```
|
|
297
306
|
|
|
298
|
-
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
### Profile Update
|
|
299
310
|
|
|
300
|
-
|
|
311
|
+
| Endpoint | Method | Auth | Description |
|
|
312
|
+
|---|---|---|---|
|
|
313
|
+
| `/store/customers/me` | PATCH | Customer JWT | Update non-contact profile fields (first_name, last_name, etc.) |
|
|
301
314
|
|
|
302
|
-
|
|
315
|
+
---
|
|
303
316
|
|
|
304
|
-
|
|
317
|
+
### Password Reset
|
|
318
|
+
|
|
319
|
+
| Endpoint | Method | Description |
|
|
320
|
+
|---|---|---|
|
|
321
|
+
| `/auth/customer/emailpass/reset-password` | POST | Request password reset email |
|
|
322
|
+
| `/auth/customer/emailpass/update` | POST | Complete password reset with token |
|
|
305
323
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
324
|
+
```bash
|
|
325
|
+
# Request reset
|
|
326
|
+
POST /auth/customer/emailpass/reset-password
|
|
327
|
+
{ "identifier": "customer@example.com" }
|
|
309
328
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
329
|
+
# Complete reset
|
|
330
|
+
POST /auth/customer/emailpass/update
|
|
331
|
+
{ "token": "<reset_token>", "password": "NewPassword123!" }
|
|
332
|
+
```
|
|
313
333
|
|
|
314
|
-
|
|
315
|
-
No auth required. Body: `{ "email": string }` (required). Looks up customer by email, verifies they have an active deletion request, sends OTP email, returns `{ token, expires_at }`.
|
|
334
|
+
---
|
|
316
335
|
|
|
317
|
-
|
|
318
|
-
Body: `{ "token": string, "code": string }`
|
|
319
|
-
Verifies OTP and cancels the request (user can request again).
|
|
336
|
+
### Account Deletion
|
|
320
337
|
|
|
321
|
-
|
|
338
|
+
| Endpoint | Method | Auth | Description |
|
|
339
|
+
|---|---|---|---|
|
|
340
|
+
| `/store/customers/account-deletion/request` | POST | Customer JWT | Request deletion — sends OTP, returns token |
|
|
341
|
+
| `/store/customers/account-deletion/confirm` | POST | — | Confirm deletion with OTP code |
|
|
342
|
+
| `/store/customers/account-deletion/cancel-request` | POST | — | Request cancellation (lookup by email/phone) |
|
|
343
|
+
| `/store/customers/account-deletion/cancel-confirm` | POST | — | Confirm cancellation with OTP code |
|
|
344
|
+
| `/admin/account-deletion-requests` | GET | Admin JWT | List deletion requests |
|
|
322
345
|
|
|
323
346
|
```bash
|
|
324
|
-
# 1. Request deletion
|
|
325
|
-
curl -X POST
|
|
326
|
-
-H "
|
|
327
|
-
-H "Authorization: Bearer YOUR_CUSTOMER_JWT" \
|
|
347
|
+
# 1. Request deletion
|
|
348
|
+
curl -X POST http://localhost:9000/store/customers/account-deletion/request \
|
|
349
|
+
-H "Authorization: Bearer $CUSTOMER_JWT" \
|
|
328
350
|
-d '{"reason": "No longer need account"}'
|
|
351
|
+
# Response: { "token": "...", "expires_at": "..." }
|
|
329
352
|
|
|
330
|
-
# 2. Confirm deletion
|
|
331
|
-
curl -X POST
|
|
332
|
-
-
|
|
333
|
-
-d '{"token": "TOKEN_FROM_EMAIL", "code": "123456"}'
|
|
353
|
+
# 2. Confirm deletion
|
|
354
|
+
curl -X POST http://localhost:9000/store/customers/account-deletion/confirm \
|
|
355
|
+
-d '{"token": "...", "code": "123456"}'
|
|
334
356
|
|
|
335
|
-
# 3. Request cancel (no auth;
|
|
336
|
-
curl -X POST
|
|
337
|
-
-H "Content-Type: application/json" \
|
|
357
|
+
# 3. Request cancel (no auth; email lookup)
|
|
358
|
+
curl -X POST http://localhost:9000/store/customers/account-deletion/cancel-request \
|
|
338
359
|
-d '{"email": "customer@example.com"}'
|
|
339
360
|
|
|
340
|
-
# 4. Confirm cancel
|
|
341
|
-
curl -X POST
|
|
342
|
-
-
|
|
343
|
-
-d '{"token": "TOKEN_FROM_EMAIL", "code": "123456"}'
|
|
361
|
+
# 4. Confirm cancel
|
|
362
|
+
curl -X POST http://localhost:9000/store/customers/account-deletion/cancel-confirm \
|
|
363
|
+
-d '{"token": "...", "code": "123456"}'
|
|
344
364
|
```
|
|
345
365
|
|
|
346
|
-
|
|
366
|
+
---
|
|
347
367
|
|
|
348
|
-
-
|
|
349
|
-
Returns `{ requests, count, offset, limit }`.
|
|
368
|
+
## Phone-only Registration & Login (`phonepass`)
|
|
350
369
|
|
|
351
|
-
|
|
370
|
+
When `registration.identifier = "phone"`, customers register and log in with phone + password only. No email address is required.
|
|
371
|
+
|
|
372
|
+
### 1. Register the provider in `medusa-config.ts`
|
|
352
373
|
|
|
353
374
|
```typescript
|
|
354
|
-
{
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
375
|
+
import { Modules } from "@medusajs/framework/utils"
|
|
376
|
+
|
|
377
|
+
export default defineConfig({
|
|
378
|
+
modules: [
|
|
379
|
+
{
|
|
380
|
+
resolve: "@medusajs/medusa/auth",
|
|
381
|
+
options: {
|
|
382
|
+
providers: [
|
|
383
|
+
{
|
|
384
|
+
resolve: "customer-registration/providers/phonepass",
|
|
385
|
+
id: "phonepass",
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
},
|
|
361
389
|
},
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
390
|
+
],
|
|
391
|
+
plugins: [
|
|
392
|
+
{
|
|
393
|
+
resolve: "customer-registration",
|
|
394
|
+
options: {
|
|
395
|
+
registration: {
|
|
396
|
+
identifier: "phone",
|
|
397
|
+
require_verification: true,
|
|
398
|
+
},
|
|
399
|
+
login: {
|
|
400
|
+
identifier: "phone",
|
|
401
|
+
},
|
|
402
|
+
phone_verification: {
|
|
403
|
+
channel: "sms",
|
|
404
|
+
},
|
|
405
|
+
},
|
|
365
406
|
},
|
|
366
|
-
|
|
367
|
-
}
|
|
407
|
+
],
|
|
408
|
+
})
|
|
368
409
|
```
|
|
369
410
|
|
|
370
|
-
|
|
411
|
+
### 2. Registration flow
|
|
371
412
|
|
|
372
|
-
|
|
413
|
+
```bash
|
|
414
|
+
# Step 1 — create phonepass auth identity
|
|
415
|
+
POST /auth/customer/phonepass/register
|
|
416
|
+
{ "phone": "+15551234567", "password": "SecretPass1!" }
|
|
417
|
+
# Response: { "token": "<pre-customer jwt>" }
|
|
373
418
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
419
|
+
# Step 2 — create customer record
|
|
420
|
+
POST /store/customers
|
|
421
|
+
Authorization: Bearer <token>
|
|
422
|
+
{ "phone": "+15551234567", "first_name": "Jane" }
|
|
378
423
|
|
|
379
|
-
|
|
424
|
+
# Step 3 — send phone OTP
|
|
425
|
+
POST /store/customers/otp/send
|
|
426
|
+
{ "customer_id": "cus_...", "type": "phone_verification" }
|
|
427
|
+
# Response: { "token": "<otp_token>", "expires_at": "..." }
|
|
428
|
+
|
|
429
|
+
# Step 4 — verify phone OTP (returns login token)
|
|
430
|
+
POST /store/customers/otp/verify
|
|
431
|
+
{ "token": "<otp_token>", "code": "482917" }
|
|
432
|
+
# Response: { "verified": true, "phone_verified": true, "token": "<login jwt>" }
|
|
433
|
+
```
|
|
380
434
|
|
|
381
|
-
|
|
435
|
+
### 3. Login flow
|
|
382
436
|
|
|
383
|
-
|
|
437
|
+
```bash
|
|
438
|
+
POST /auth/customer/phonepass
|
|
439
|
+
{ "phone": "+15551234567", "password": "SecretPass1!" }
|
|
440
|
+
# Response: { "token": "<jwt>" }
|
|
441
|
+
```
|
|
384
442
|
|
|
385
|
-
###
|
|
443
|
+
### 4. Update phone (after registration)
|
|
386
444
|
|
|
387
445
|
```bash
|
|
388
|
-
|
|
446
|
+
# Request change
|
|
447
|
+
POST /store/customers/me/contact
|
|
448
|
+
Authorization: Bearer <login jwt>
|
|
449
|
+
{ "phone": "+15559998888" }
|
|
450
|
+
# Response: { "token": "<otp_token>", "expires_at": "..." }
|
|
451
|
+
|
|
452
|
+
# Verify and apply
|
|
453
|
+
POST /store/customers/me/contact/verify
|
|
454
|
+
Authorization: Bearer <login jwt>
|
|
455
|
+
{ "token": "<otp_token>", "code": "739201" }
|
|
456
|
+
# Response: { "customer": { "phone": "+15559998888", ... }, "token": "<fresh jwt>" }
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## Modules
|
|
462
|
+
|
|
463
|
+
The plugin includes three modules:
|
|
464
|
+
|
|
465
|
+
1. **`otp-verification`** — OTP generation, verification, and management
|
|
466
|
+
2. **`customer-registration`** — Customer registration logic and route overrides
|
|
467
|
+
3. **`account-deletion-request`** — Account deletion request lifecycle (pending → confirmed → completed/cancelled)
|
|
468
|
+
|
|
469
|
+
## Workflows
|
|
470
|
+
|
|
471
|
+
| Workflow | Description |
|
|
472
|
+
|---|---|
|
|
473
|
+
| `verify-email` | Sets `email_verified = true` on the customer record |
|
|
474
|
+
| `verify-phone` | Sets `phone_verified = true` on the customer record |
|
|
475
|
+
| `update-contact` | Updates `customer.phone` / `customer.email`, sets verified flag, syncs `provider_identity.entity_id` when applicable |
|
|
476
|
+
| `send-otp` | Resolves channel config, generates OTP, sends notification |
|
|
477
|
+
| `send-contact-change-otp` | Generates OTP with `new_value` embedded in JWT, sends to the new contact address |
|
|
478
|
+
| `change-password` | Updates customer password via the auth module |
|
|
479
|
+
|
|
480
|
+
## Database Migrations
|
|
481
|
+
|
|
482
|
+
| Migration | Description |
|
|
483
|
+
|---|---|
|
|
484
|
+
| `Migration20250120000000AddCustomerVerificationColumns` | Adds `email_verified` and `phone_verified` columns to the customer table |
|
|
485
|
+
| `Migration20250118001000CreateOtpVerificationTable` | Creates `otp_verification` table |
|
|
486
|
+
| `Migration20250221000000CreateAccountDeletionRequestTable` | Creates `account_deletion_request` table |
|
|
487
|
+
| `Migration20250221000000AddAccountDeletionOtpPurposes` | Extends `otp_verification.purpose` enum for account deletion flows |
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
npx medusa db:migrate
|
|
389
491
|
```
|
|
390
492
|
|
|
391
|
-
|
|
493
|
+
## Password Reset (Helper Functions)
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
import {
|
|
497
|
+
requestPasswordReset,
|
|
498
|
+
completePasswordReset,
|
|
499
|
+
} from "customer-registration/helpers"
|
|
500
|
+
|
|
501
|
+
await requestPasswordReset(
|
|
502
|
+
{ email: "customer@example.com" },
|
|
503
|
+
{ baseUrl: "https://store.example.com", publishableApiKey: "pk_..." }
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
await completePasswordReset(
|
|
507
|
+
{
|
|
508
|
+
email: "customer@example.com",
|
|
509
|
+
password: "NewPassword123!",
|
|
510
|
+
token: "reset_token_from_email",
|
|
511
|
+
},
|
|
512
|
+
{ baseUrl: "https://store.example.com", publishableApiKey: "pk_..." }
|
|
513
|
+
)
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## Requirements
|
|
517
|
+
|
|
518
|
+
- Medusa v2.11.2 or higher
|
|
519
|
+
- Node.js >= 20
|
|
520
|
+
- Notification module configured with at least one provider (email/SMS)
|
|
521
|
+
- Database migrations applied (`npx medusa db:migrate`)
|
|
522
|
+
- `phonepass` provider registered in auth module (required when `registration.identifier` is `"phone"` or `"both"`)
|
|
523
|
+
|
|
524
|
+
## Documentation
|
|
525
|
+
|
|
526
|
+
- **[USAGE.md](./USAGE.md)** — Complete usage guide with examples
|
|
527
|
+
- **[README.md](./README.md)** — This file (overview and quick start)
|
|
528
|
+
|
|
529
|
+
## Development
|
|
392
530
|
|
|
393
531
|
```bash
|
|
532
|
+
# Build
|
|
533
|
+
npm run build
|
|
534
|
+
|
|
535
|
+
# Watch mode
|
|
394
536
|
npx medusa plugin:develop
|
|
395
537
|
```
|
|
396
538
|
|
|
397
|
-
This command watches for changes and automatically rebuilds and publishes the plugin to the local registry.
|
|
398
|
-
|
|
399
539
|
## License
|
|
400
540
|
|
|
401
541
|
MIT
|