Skip to content

feat: phone auth + twilio integration #58

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ To learn how to use an example, open its `README.md` file. You'll find the detai
| [Loyalty Points System](./loyalty-points/README.md) | Allow customers to earn and redeem loyalty points. |
| [Marketplace](./marketplace/README.md) | Allow vendors to register and sell products. |
| [Migrate from Magento](./migrate-from-magento/README.md) | Migrate products and categories from Magento |
| [Phone Authentication + Twilio SMS Integration](./phone-auth/README.md) | Authenticate users with their phone number + send OTPs with Twilio. |
| [Product Reviews](./product-reviews/README.md) | Allow customers to add product reviews, and merchants to manage them. |
| [Quotes Management](./quotes-management/README.md) | Allow customers to send quotes, and merchants to manage and accept them. |
| [Re-order Feature](./re-order/README.md) | Allow customers to re-order a previous order. |
Expand Down
13 changes: 13 additions & 0 deletions phone-auth/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
STORE_CORS=http://localhost:8000,https://docs.medusajs.com
ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com
AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com
REDIS_URL=redis://localhost:6379
JWT_SECRET=supersecret
COOKIE_SECRET=supersecret
DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary
DB_NAME=medusa-phone-auth
POSTGRES_URL=

TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_FROM=
26 changes: 26 additions & 0 deletions phone-auth/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/dist
.env
.DS_Store
/uploads
/node_modules
yarn-error.log

.idea

coverage

!src/**

./tsconfig.tsbuildinfo
medusa-db.sql
build
.cache

.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

.medusa
1 change: 1 addition & 0 deletions phone-auth/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
201 changes: 201 additions & 0 deletions phone-auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# Medusa v2 Example: Phone Authentication + Twilio SMS Integration

This directory holds the code for the [Phone Authentication Tutorial](https://docs.medusajs.com/resources/how-to-tutorials/tutorials/phone-auth).

> You can use both the Phone Authentication Module Provider and Twilio SMS Notification Module Provider, or just one of them. They're not tightly coupled and there are no side effects to not using them together. However, if you don't use Twilio SMS, you need another way to send the OTP to users.

You can either:

- [install and use it as a Medusa application](#installation);
- or [copy its source files into an existing Medusa application](#copy-into-existing-medusa-application).

## Prerequisites

- [Node.js v20+](https://nodejs.org/en/download)
- [Git CLI](https://git-scm.com/downaloads)
- [PostgreSQL](https://www.postgresql.org/download/)
- If you're using Twilio, you need:
- [Twilio account](https://console.twilio.com/)
- [Phone number](https://www.twilio.com/docs/phone-numbers)
- [Account SID](https://www.twilio.com/docs/usage/tutorials/how-to-use-your-free-trial-account-namer#console-dashboard-home-page)
- [Auth token](https://www.twilio.com/docs/usage/tutorials/how-to-use-your-free-trial-account-namer#console-dashboard-home-page)

## Installation

1. Clone the repository and change to the `phone-auth` directory:

```bash
git clone https://github.com/medusajs/examples.git
cd examples/phone-auth
```

2\. Rename the `.env.template` file to `.env`.

3\. If necessary, change the PostgreSQL username, password, and host in the `DATABASE_URL` environment variable.

4\. Set the Twilio environment variables:

```bash
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_FROM=
```

Where:

- `TWILIO_ACCOUNT_SID` is the account SID
- `TWILIO_AUTH_TOKEN` is the auth token
- `TWILIO_FROM` is the phone number to send SMS from.

5\. Install dependencies:

```bash
yarn # or npm install
```

6\. Setup and seed the database:

```bash
npx medusa db:setup
yarn seed # or npm run seed
```

7\. Start the Medusa application:

```bash
yarn dev # or npm run dev
```

You can test out the phone authentication and Twilio SMS integration using the OpenAPI Specs/Postman collection in the [more resources](#more-resources) section.

### Use Phone Authentication Module Provider with Actor Types

This project is configured to allow only customers to authenticate with the `phone-auth` provider. To enable it for other actor types, such as admin user or vendors, change the `projectConfig.http.authMethodsPerActor` configuration in `medusa-config.ts`:

```ts
module.exports = defineConfig({
projectConfig: {
// ...
http: {
// ...
authMethodsPerActor: {
user: ["emailpass", "phone-auth"], // enable for admin users
customer: ["emailpass", "phone-auth"],
},
},
},
// ...
})
```

## Copy into Existing Medusa Application

If you have an existing Medusa application, copy the following directories and files into your project:

- `src/api/middlewares.ts`
- `src/modules/phone-auth`
- `src/modules/twilio-sms`
- `src/subscribers`

Then, add the providers to `medusa-config.ts`:

```ts
module.exports = defineConfig({
// ...
modules: [
{
resolve: "@medusajs/medusa/auth",
dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER, Modules.EVENT_BUS],
options: {
providers: [
// default provider
{
resolve: "@medusajs/medusa/auth-emailpass",
id: "emailpass",
},
{
resolve: "./src/modules/phone-auth",
id: "phone-auth",
options: {
jwtSecret: process.env.PHONE_AUTH_JWT_SECRET || "supersecret",
},
},
],
},
},
{
resolve: "@medusajs/medusa/notification",
options: {
providers: [
// default provider
{
resolve: "@medusajs/medusa/notification-local",
id: "local",
options: {
name: "Local Notification Provider",
channels: ["feed"],
},
},
{
resolve: "./src/modules/twilio-sms",
id: "twilio-sms",
options: {
channels: ["sms"],
accountSid: process.env.TWILIO_ACCOUNT_SID,
authToken: process.env.TWILIO_AUTH_TOKEN,
from: process.env.TWILIO_FROM,
},
},
],
},
}
]
})
```

Next, add the following environment variables:

```bash
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_FROM=
```

Where:

- `TWILIO_ACCOUNT_SID` is the account SID
- `TWILIO_AUTH_TOKEN` is the auth token
- `TWILIO_FROM` is the phone number to send SMS from.

Also, add in `projectConfig.http.authMethodsPerActor` the actor types to enable the `phone-auth` provider for customers:

```ts
module.exports = defineConfig({
projectConfig: {
// ...
http: {
// ...
authMethodsPerActor: {
user: ["emailpass"],
customer: ["emailpass", "phone-auth"],
},
},
},
// ...
})
```

You can also enable it for other actor types (admin user, vendor, etc...)

After that, install the `twilio` and `jsonwebtoken` dependencies package:

```bash
yarn add twilio jsonwebtoken # or npm install twilio jsonwebtoken
yarn add @types/jsonwebtoken -D # or npm install @types/jsonwebtoken --save-dev
```

## More Resources

- [Medusa Documentatin](https://docs.medusajs.com)
- [Twilio Documentation](https://www.twilio.com/docs)
- [OpenAPI Spec file](https://res.cloudinary.com/dza7lstvk/raw/upload/v1747745832/OpenApi/Phone_Auth_g4xsqv.yaml): Can be imported into tools like Postman to view and send requests to this project's API routes.
24 changes: 24 additions & 0 deletions phone-auth/instrumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Uncomment this file to enable instrumentation and observability using OpenTelemetry
// Refer to the docs for installation instructions: https://docs.medusajs.com/learn/debugging-and-testing/instrumentation

// import { registerOtel } from "@medusajs/medusa"
// // If using an exporter other than Zipkin, require it here.
// import { ZipkinExporter } from "@opentelemetry/exporter-zipkin"

// // If using an exporter other than Zipkin, initialize it here.
// const exporter = new ZipkinExporter({
// serviceName: 'my-medusa-project',
// })

// export function register() {
// registerOtel({
// serviceName: 'medusajs',
// // pass exporter
// exporter,
// instrument: {
// http: true,
// workflows: true,
// query: true
// },
// })
// }
29 changes: 29 additions & 0 deletions phone-auth/integration-tests/http/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Integration Tests

The `medusa-test-utils` package provides utility functions to create integration tests for your API routes and workflows.

For example:

```ts
import { medusaIntegrationTestRunner } from "medusa-test-utils"

medusaIntegrationTestRunner({
testSuite: ({ api, getContainer }) => {
describe("Custom endpoints", () => {
describe("GET /store/custom", () => {
it("returns correct message", async () => {
const response = await api.get(
`/store/custom`
)

expect(response.status).toEqual(200)
expect(response.data).toHaveProperty("message")
expect(response.data.message).toEqual("Hello, World!")
})
})
})
}
})
```

Learn more in [this documentation](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/integration-tests).
15 changes: 15 additions & 0 deletions phone-auth/integration-tests/http/health.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
jest.setTimeout(60 * 1000)

medusaIntegrationTestRunner({
inApp: true,
env: {},
testSuite: ({ api }) => {
describe("Ping", () => {
it("ping the server health endpoint", async () => {
const response = await api.get('/health')
expect(response.status).toEqual(200)
})
})
},
})
3 changes: 3 additions & 0 deletions phone-auth/integration-tests/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { MetadataStorage } = require("@mikro-orm/core")

MetadataStorage.clear()
27 changes: 27 additions & 0 deletions phone-auth/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { loadEnv } = require("@medusajs/utils");
loadEnv("test", process.cwd());

module.exports = {
transform: {
"^.+\\.[jt]s$": [
"@swc/jest",
{
jsc: {
parser: { syntax: "typescript", decorators: true },
},
},
],
},
testEnvironment: "node",
moduleFileExtensions: ["js", "ts", "json"],
modulePathIgnorePatterns: ["dist/", "<rootDir>/.medusa/"],
setupFiles: ["./integration-tests/setup.js"],
};

if (process.env.TEST_TYPE === "integration:http") {
module.exports.testMatch = ["**/integration-tests/http/*.spec.[jt]s"];
} else if (process.env.TEST_TYPE === "integration:modules") {
module.exports.testMatch = ["**/src/modules/*/__tests__/**/*.[jt]s"];
} else if (process.env.TEST_TYPE === "unit") {
module.exports.testMatch = ["**/src/**/__tests__/**/*.unit.spec.[jt]s"];
}
Loading