Adds endpoints to manage customers (#54)
This commit is contained in:
@@ -3,10 +3,6 @@ export default async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const userService = req.scope.resolve("userService")
|
const userService = req.scope.resolve("userService")
|
||||||
const token = await userService.generateResetPasswordToken(user_id)
|
const token = await userService.generateResetPasswordToken(user_id)
|
||||||
if (!token) {
|
|
||||||
res.sendStatus(404)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
res.json(token)
|
res.json(token)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import jwt from "jsonwebtoken"
|
||||||
|
import { Validator } from "medusa-core-utils"
|
||||||
|
import config from "../../../../config"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { body } = req
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
email: Validator.string().required(),
|
||||||
|
password: Validator.string().required(),
|
||||||
|
})
|
||||||
|
const { value, error } = schema.validate(body)
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
const authService = req.scope.resolve("authService")
|
||||||
|
const result = await authService.authenticateCustomer(
|
||||||
|
value.email,
|
||||||
|
value.password
|
||||||
|
)
|
||||||
|
if (!result.success) {
|
||||||
|
res.sendStatus(401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add JWT to cookie
|
||||||
|
req.session.jwt = jwt.sign(
|
||||||
|
{ customer_id: result.user._id },
|
||||||
|
config.jwtSecret,
|
||||||
|
{
|
||||||
|
expiresIn: "30d",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
res.json(result.customer)
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { CustomerServiceMock } from "../../../../../services/__mocks__/customer"
|
||||||
|
|
||||||
|
describe("POST /store/customers", () => {
|
||||||
|
describe("successfully creates a customer", () => {
|
||||||
|
let subject
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("POST", `/store/customers`, {
|
||||||
|
payload: {
|
||||||
|
email: "lebron@james.com",
|
||||||
|
first_name: "LeBron",
|
||||||
|
last_name: "James",
|
||||||
|
password: "TheGame",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CustomerService create", () => {
|
||||||
|
expect(CustomerServiceMock.create).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CustomerServiceMock.create).toHaveBeenCalledWith({
|
||||||
|
email: "lebron@james.com",
|
||||||
|
first_name: "LeBron",
|
||||||
|
last_name: "James",
|
||||||
|
password: "TheGame",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns customer decorated", () => {
|
||||||
|
expect(subject.body.email).toEqual("lebron@james.com")
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("fails if missing field", () => {
|
||||||
|
let subject
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("POST", `/store/customers`, {
|
||||||
|
payload: {
|
||||||
|
first_name: "LeBron",
|
||||||
|
last_name: "James",
|
||||||
|
password: "TheGame",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns product decorated", () => {
|
||||||
|
expect(subject.body.name).toEqual("invalid_data")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import jwt from "jsonwebtoken"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { CustomerServiceMock } from "../../../../../services/__mocks__/customer"
|
||||||
|
|
||||||
|
describe("POST /store/customers/password-token", () => {
|
||||||
|
describe("successfully creates a customer", () => {
|
||||||
|
let subject
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("POST", `/store/customers/password-token`, {
|
||||||
|
payload: {
|
||||||
|
email: "lebron@james.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CustomerService retrieve", () => {
|
||||||
|
expect(CustomerServiceMock.retrieveByEmail).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CustomerServiceMock.retrieveByEmail).toHaveBeenCalledWith(
|
||||||
|
"lebron@james.com"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CustomerService retrieve", () => {
|
||||||
|
expect(
|
||||||
|
CustomerServiceMock.generateResetPasswordToken
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
CustomerServiceMock.generateResetPasswordToken
|
||||||
|
).toHaveBeenCalledWith(IdMap.getId("lebron"))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns customer decorated", () => {
|
||||||
|
expect(subject.status).toEqual(204)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import jwt from "jsonwebtoken"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { CustomerServiceMock } from "../../../../../services/__mocks__/customer"
|
||||||
|
|
||||||
|
describe("POST /store/customers/password-reset", () => {
|
||||||
|
describe("successfully creates a customer", () => {
|
||||||
|
let subject
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("POST", `/store/customers/password-reset`, {
|
||||||
|
payload: {
|
||||||
|
email: "lebron@james.com",
|
||||||
|
token: jwt.sign({ customer_id: IdMap.getId("lebron") }, "1234"),
|
||||||
|
password: "TheGame",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CustomerService create", () => {
|
||||||
|
expect(CustomerServiceMock.update).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CustomerServiceMock.update).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("lebron"),
|
||||||
|
{
|
||||||
|
password: "TheGame",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns customer decorated", () => {
|
||||||
|
expect(subject.body.email).toEqual("lebron@james.com")
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("fails if id in webtoken not matching", () => {
|
||||||
|
let subject
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("POST", `/store/customers/password-reset`, {
|
||||||
|
payload: {
|
||||||
|
email: "lebron@james.com",
|
||||||
|
token: jwt.sign({ customer_id: IdMap.getId("not-lebron") }, "1234"),
|
||||||
|
password: "TheGame",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails", () => {
|
||||||
|
expect(subject.status).toEqual(401)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { CustomerServiceMock } from "../../../../../services/__mocks__/customer"
|
||||||
|
|
||||||
|
describe("POST /store/customers/:id", () => {
|
||||||
|
describe("successfully updates a customer", () => {
|
||||||
|
let subject
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/customers/${IdMap.getId("lebron")}`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
first_name: "LeBron",
|
||||||
|
last_name: "James",
|
||||||
|
},
|
||||||
|
clientSession: {
|
||||||
|
jwt: {
|
||||||
|
customer_id: IdMap.getId("lebron"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CustomerService create", () => {
|
||||||
|
expect(CustomerServiceMock.update).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CustomerServiceMock.update).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("lebron"),
|
||||||
|
{
|
||||||
|
first_name: "LeBron",
|
||||||
|
last_name: "James",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns product decorated", () => {
|
||||||
|
expect(subject.body.first_name).toEqual("LeBron")
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("fails if not authenticated", () => {
|
||||||
|
let subject
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/customers/${IdMap.getId("customer1")}`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
first_name: "LeBron",
|
||||||
|
last_name: "James",
|
||||||
|
},
|
||||||
|
clientSession: {
|
||||||
|
jwt: {
|
||||||
|
customer_id: IdMap.getId("lebron"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("status code 400", () => {
|
||||||
|
expect(subject.status).toEqual(400)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { CustomerServiceMock } from "../../../../../services/__mocks__/customer"
|
||||||
|
|
||||||
|
describe("POST /store/customers/:id/password", () => {
|
||||||
|
describe("successfully updates a customer", () => {
|
||||||
|
let subject
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/customers/${IdMap.getId("lebron")}/password`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
password: "NewPass",
|
||||||
|
},
|
||||||
|
clientSession: {
|
||||||
|
jwt: {
|
||||||
|
customer_id: IdMap.getId("lebron"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls CustomerService update", () => {
|
||||||
|
expect(CustomerServiceMock.update).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CustomerServiceMock.update).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("lebron"),
|
||||||
|
{
|
||||||
|
password: "NewPass",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns product decorated", () => {
|
||||||
|
expect(subject.body.first_name).toEqual("LeBron")
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("fails if not authenticated", () => {
|
||||||
|
let subject
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/store/customers/${IdMap.getId("customer1")}/password`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
first_name: "LeBron",
|
||||||
|
last_name: "James",
|
||||||
|
},
|
||||||
|
clientSession: {
|
||||||
|
jwt: {
|
||||||
|
customer_id: IdMap.getId("lebron"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("status code 400", () => {
|
||||||
|
expect(subject.status).toEqual(400)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res, next, id) => {
|
||||||
|
if (!(req.user && req.user.customer_id === id)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"You must be logged in to update"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
email: Validator.string()
|
||||||
|
.email()
|
||||||
|
.required(),
|
||||||
|
first_name: Validator.string().required(),
|
||||||
|
last_name: Validator.string().required(),
|
||||||
|
password: Validator.string().required(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const customerService = req.scope.resolve("customerService")
|
||||||
|
const customer = await customerService.create(value)
|
||||||
|
|
||||||
|
const data = await customerService.decorate(customer)
|
||||||
|
res.status(201).json(data)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Router } from "express"
|
||||||
|
import middlewares from "../../../middlewares"
|
||||||
|
|
||||||
|
const route = Router()
|
||||||
|
|
||||||
|
export default app => {
|
||||||
|
app.use("/customers", route)
|
||||||
|
|
||||||
|
route.post("/", middlewares.wrap(require("./create-customer").default))
|
||||||
|
route.post(
|
||||||
|
"/password-reset",
|
||||||
|
middlewares.wrap(require("./reset-password").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/password-token",
|
||||||
|
middlewares.wrap(require("./reset-password-token").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Authenticated endpoints
|
||||||
|
route.use(middlewares.authenticate())
|
||||||
|
|
||||||
|
route.param("id", middlewares.wrap(require("./authorize-customer").default))
|
||||||
|
|
||||||
|
route.post("/:id", middlewares.wrap(require("./update-customer").default))
|
||||||
|
route.post(
|
||||||
|
"/:id/password",
|
||||||
|
middlewares.wrap(require("./update-password").default)
|
||||||
|
)
|
||||||
|
return app
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
email: Validator.string()
|
||||||
|
.email()
|
||||||
|
.required(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const customerService = req.scope.resolve("customerService")
|
||||||
|
const customer = await customerService.retrieveByEmail(value.email)
|
||||||
|
|
||||||
|
await customerService.generateResetPasswordToken(customer._id)
|
||||||
|
|
||||||
|
res.sendStatus(204)
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
|
import jwt from "jsonwebtoken"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
email: Validator.string()
|
||||||
|
.email()
|
||||||
|
.required(),
|
||||||
|
token: Validator.string().required(),
|
||||||
|
password: Validator.string().required(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const customerService = req.scope.resolve("customerService")
|
||||||
|
const customer = await customerService.retrieveByEmail(value.email)
|
||||||
|
|
||||||
|
const decodedToken = await jwt.verify(value.token, customer.password_hash)
|
||||||
|
if (!decodedToken || decodedToken.customer_id !== customer._id) {
|
||||||
|
res.status(401).send("Invalid or expired password reset token")
|
||||||
|
}
|
||||||
|
|
||||||
|
await customerService.update(customer._id, { password: value.password })
|
||||||
|
|
||||||
|
const updated = await customerService.retrieve(customer._id)
|
||||||
|
const data = await customerService.decorate(customer)
|
||||||
|
res.status(200).json(data)
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
first_name: Validator.string(),
|
||||||
|
last_name: Validator.string(),
|
||||||
|
password: Validator.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const customerService = req.scope.resolve("customerService")
|
||||||
|
await customerService.update(id, value)
|
||||||
|
|
||||||
|
const customer = await customerService.retrieve(id)
|
||||||
|
const data = await customerService.decorate(customer)
|
||||||
|
res.status(200).json(data)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
password: Validator.string().required(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const customerService = req.scope.resolve("customerService")
|
||||||
|
|
||||||
|
await customerService.update(id, value)
|
||||||
|
|
||||||
|
const customer = await customerService.retrieve(id)
|
||||||
|
const data = await customerService.decorate(customer)
|
||||||
|
res.status(200).json(data)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Router } from "express"
|
import { Router } from "express"
|
||||||
|
|
||||||
import productRoutes from "./products"
|
import productRoutes from "./products"
|
||||||
import cartRoutes from "./carts"
|
import cartRoutes from "./carts"
|
||||||
|
import customerRoutes from "./customers"
|
||||||
import shippingOptionRoutes from "./shipping-options"
|
import shippingOptionRoutes from "./shipping-options"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
@@ -8,6 +10,7 @@ const route = Router()
|
|||||||
export default app => {
|
export default app => {
|
||||||
app.use("/store", route)
|
app.use("/store", route)
|
||||||
|
|
||||||
|
customerRoutes(route)
|
||||||
productRoutes(route)
|
productRoutes(route)
|
||||||
cartRoutes(route)
|
cartRoutes(route)
|
||||||
shippingOptionRoutes(route)
|
shippingOptionRoutes(route)
|
||||||
|
|||||||
@@ -69,6 +69,16 @@ export async function request(method, url, opts = {}) {
|
|||||||
// console.log(sessions.util.decode(adminSessionOpts, opts.headers.Cookie))
|
// console.log(sessions.util.decode(adminSessionOpts, opts.headers.Cookie))
|
||||||
}
|
}
|
||||||
if (opts.clientSession) {
|
if (opts.clientSession) {
|
||||||
|
if (opts.clientSession.jwt) {
|
||||||
|
opts.clientSession.jwt = jwt.sign(
|
||||||
|
opts.clientSession.jwt,
|
||||||
|
config.jwtSecret,
|
||||||
|
{
|
||||||
|
expiresIn: "30d",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
headers.Cookie +=
|
headers.Cookie +=
|
||||||
clientSessionOpts.cookieName +
|
clientSessionOpts.cookieName +
|
||||||
"=" +
|
"=" +
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ export const CustomerModelMock = {
|
|||||||
}),
|
}),
|
||||||
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
|
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
findOne: jest.fn().mockImplementation(query => {
|
findOne: jest.fn().mockImplementation(query => {
|
||||||
|
if (query.email === "oliver@medusa.com") {
|
||||||
|
return Promise.resolve(customers.testCustomer)
|
||||||
|
}
|
||||||
if (query._id === IdMap.getId("testCustomer")) {
|
if (query._id === IdMap.getId("testCustomer")) {
|
||||||
return Promise.resolve(customers.testCustomer)
|
return Promise.resolve(customers.testCustomer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ class CustomerModel extends BaseModel {
|
|||||||
first_name: { type: String, required: true },
|
first_name: { type: String, required: true },
|
||||||
last_name: { type: String, required: true },
|
last_name: { type: String, required: true },
|
||||||
billing_address: { type: AddressSchema },
|
billing_address: { type: AddressSchema },
|
||||||
|
password_hash: { type: String },
|
||||||
|
has_account: { type: Boolean, default: false },
|
||||||
|
orders: { type: [String], default: [] },
|
||||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import bcrypt from "bcrypt"
|
||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
|
||||||
|
export const CustomerServiceMock = {
|
||||||
|
create: jest.fn().mockImplementation(data => {
|
||||||
|
return Promise.resolve(data)
|
||||||
|
}),
|
||||||
|
update: jest.fn().mockImplementation((id, data) => {
|
||||||
|
return Promise.resolve(data)
|
||||||
|
}),
|
||||||
|
decorate: jest.fn().mockImplementation(data => {
|
||||||
|
let d = Object.assign({}, data)
|
||||||
|
d.decorated = true
|
||||||
|
return d
|
||||||
|
}),
|
||||||
|
generateResetPasswordToken: jest.fn().mockImplementation(id => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
retrieve: jest.fn().mockImplementation(id => {
|
||||||
|
if (id === IdMap.getId("lebron")) {
|
||||||
|
return Promise.resolve({
|
||||||
|
_id: IdMap.getId("lebron"),
|
||||||
|
first_name: "LeBron",
|
||||||
|
last_name: "James",
|
||||||
|
email: "lebron@james.com",
|
||||||
|
password_hash: "1234",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
retrieveByEmail: jest.fn().mockImplementation(email => {
|
||||||
|
if (email === "lebron@james.com") {
|
||||||
|
return Promise.resolve({
|
||||||
|
_id: IdMap.getId("lebron"),
|
||||||
|
email,
|
||||||
|
password_hash: "1234",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (email === "oliver@test.dk") {
|
||||||
|
return bcrypt
|
||||||
|
.hash("123456789", 10)
|
||||||
|
.then(hash => ({ email, password_hash: hash }))
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const mock = jest.fn().mockImplementation(() => {
|
||||||
|
return CustomerServiceMock
|
||||||
|
})
|
||||||
|
|
||||||
|
export default mock
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import AuthService from "../auth"
|
import AuthService from "../auth"
|
||||||
import { users, UserServiceMock } from "../__mocks__/user"
|
import { users, UserServiceMock } from "../__mocks__/user"
|
||||||
|
import { customers, CustomerServiceMock } from "../__mocks__/customer"
|
||||||
|
|
||||||
describe("AuthService", () => {
|
describe("AuthService", () => {
|
||||||
describe("authenticate", () => {
|
describe("authenticate", () => {
|
||||||
@@ -31,6 +32,35 @@ describe("AuthService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("authenticateCustomer", () => {
|
||||||
|
let authService
|
||||||
|
authService = new AuthService({ customerService: CustomerServiceMock })
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns success and user when passwords match", async () => {
|
||||||
|
const result = await authService.authenticateCustomer(
|
||||||
|
"oliver@test.dk",
|
||||||
|
"123456789"
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result.success).toEqual(true)
|
||||||
|
expect(result.customer.email).toEqual("oliver@test.dk")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns failure when passwords don't match", async () => {
|
||||||
|
const result = await authService.authenticateCustomer(
|
||||||
|
"oliver@test.dk",
|
||||||
|
"invalid-password"
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result.success).toEqual(false)
|
||||||
|
expect(result.error).toEqual("Invalid email or password")
|
||||||
|
expect(result.customer).toEqual(undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("authenticateAPIToken", () => {
|
describe("authenticateAPIToken", () => {
|
||||||
let authService
|
let authService
|
||||||
authService = new AuthService({ userService: UserServiceMock })
|
authService = new AuthService({ userService: UserServiceMock })
|
||||||
|
|||||||
@@ -26,6 +26,28 @@ describe("CustomerService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("retrieveByEmail", () => {
|
||||||
|
let result
|
||||||
|
beforeAll(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
const customerService = new CustomerService({
|
||||||
|
customerModel: CustomerModelMock,
|
||||||
|
})
|
||||||
|
result = await customerService.retrieveByEmail("oliver@medusa.com")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls customer model functions", () => {
|
||||||
|
expect(CustomerModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CustomerModelMock.findOne).toHaveBeenCalledWith({
|
||||||
|
email: "oliver@medusa.com",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the customer", () => {
|
||||||
|
expect(result).toEqual(customers.testCustomer)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("setMetadata", () => {
|
describe("setMetadata", () => {
|
||||||
const customerService = new CustomerService({
|
const customerService = new CustomerService({
|
||||||
customerModel: CustomerModelMock,
|
customerModel: CustomerModelMock,
|
||||||
@@ -68,8 +90,8 @@ describe("CustomerService", () => {
|
|||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls model layer create", () => {
|
it("calls model layer create", async () => {
|
||||||
customerService.create({
|
await customerService.create({
|
||||||
email: "oliver@medusa.com",
|
email: "oliver@medusa.com",
|
||||||
first_name: "Oliver",
|
first_name: "Oliver",
|
||||||
last_name: "Juhl",
|
last_name: "Juhl",
|
||||||
@@ -83,20 +105,38 @@ describe("CustomerService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails if email is in incorrect format", () => {
|
it("calls model layer create", async () => {
|
||||||
try {
|
await customerService.create({
|
||||||
|
email: "oliver@medusa.com",
|
||||||
|
first_name: "Oliver",
|
||||||
|
last_name: "Juhl",
|
||||||
|
password: "secretsauce",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(CustomerModelMock.create).toBeCalledTimes(1)
|
||||||
|
expect(CustomerModelMock.create).toBeCalledWith({
|
||||||
|
email: "oliver@medusa.com",
|
||||||
|
first_name: "Oliver",
|
||||||
|
last_name: "Juhl",
|
||||||
|
has_account: true,
|
||||||
|
password_hash: expect.stringMatching(
|
||||||
|
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails if email is in incorrect format", async () => {
|
||||||
|
expect(
|
||||||
customerService.create({
|
customerService.create({
|
||||||
email: "olivermedusa.com",
|
email: "olivermedusa.com",
|
||||||
first_name: "Oliver",
|
first_name: "Oliver",
|
||||||
last_name: "Juhl",
|
last_name: "Juhl",
|
||||||
})
|
})
|
||||||
} catch (error) {
|
).rejects.toThrow("The email is not valid")
|
||||||
expect(error.message).toEqual("The email is not valid")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails if billing address is in incorrect format", () => {
|
it("fails if billing address is in incorrect format", () => {
|
||||||
try {
|
expect(
|
||||||
customerService.create({
|
customerService.create({
|
||||||
email: "oliver@medusa.com",
|
email: "oliver@medusa.com",
|
||||||
first_name: "Oliver",
|
first_name: "Oliver",
|
||||||
@@ -105,9 +145,7 @@ describe("CustomerService", () => {
|
|||||||
first_name: 1234,
|
first_name: 1234,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
).rejects.toThrow("The address is not valid")
|
||||||
expect(error.message).toEqual("The address is not valid")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -124,12 +162,19 @@ describe("CustomerService", () => {
|
|||||||
await customerService.update(IdMap.getId("testCustomer"), {
|
await customerService.update(IdMap.getId("testCustomer"), {
|
||||||
first_name: "Olli",
|
first_name: "Olli",
|
||||||
last_name: "Test",
|
last_name: "Test",
|
||||||
|
email: "oliver@medusa2.com",
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(CustomerModelMock.updateOne).toBeCalledTimes(1)
|
expect(CustomerModelMock.updateOne).toBeCalledTimes(1)
|
||||||
expect(CustomerModelMock.updateOne).toBeCalledWith(
|
expect(CustomerModelMock.updateOne).toBeCalledWith(
|
||||||
{ _id: IdMap.getId("testCustomer") },
|
{ _id: IdMap.getId("testCustomer") },
|
||||||
{ $set: { first_name: "Olli", last_name: "Test" } },
|
{
|
||||||
|
$set: {
|
||||||
|
first_name: "Olli",
|
||||||
|
last_name: "Test",
|
||||||
|
email: "oliver@medusa2.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
{ runValidators: true }
|
{ runValidators: true }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -144,118 +189,62 @@ describe("CustomerService", () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails if billing address updates are attempted", async () => {
|
it("updates with billing address", async () => {
|
||||||
try {
|
await customerService.update(IdMap.getId("testCustomer"), {
|
||||||
await customerService.update(IdMap.getId("testCustomer"), {
|
first_name: "Olli",
|
||||||
billing_address: {
|
last_name: "Test",
|
||||||
last_name: "nnonono",
|
billing_address: {
|
||||||
},
|
first_name: "Olli",
|
||||||
})
|
last_name: "Juhl",
|
||||||
} catch (err) {
|
address_1: "Laksegade",
|
||||||
expect(err.message).toEqual(
|
city: "Copenhagen",
|
||||||
"Use updateBillingAddress to update billing address"
|
country_code: "DK",
|
||||||
)
|
postal_code: "2100",
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("updateEmail", () => {
|
|
||||||
const customerService = new CustomerService({
|
|
||||||
customerModel: CustomerModelMock,
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully updates an email", async () => {
|
|
||||||
await customerService.updateEmail(
|
|
||||||
IdMap.getId("testCustomer"),
|
|
||||||
"oliver@medusa2.com"
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(CustomerModelMock.updateOne).toHaveBeenCalledTimes(1)
|
|
||||||
expect(CustomerModelMock.updateOne).toHaveBeenCalledWith(
|
|
||||||
{
|
|
||||||
_id: IdMap.getId("testCustomer"),
|
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(CustomerModelMock.updateOne).toBeCalledTimes(1)
|
||||||
|
expect(CustomerModelMock.updateOne).toBeCalledWith(
|
||||||
|
{ _id: IdMap.getId("testCustomer") },
|
||||||
{
|
{
|
||||||
$set: { email: "oliver@medusa2.com" },
|
$set: {
|
||||||
}
|
first_name: "Olli",
|
||||||
|
last_name: "Test",
|
||||||
|
billing_address: {
|
||||||
|
first_name: "Olli",
|
||||||
|
last_name: "Juhl",
|
||||||
|
address_1: "Laksegade",
|
||||||
|
city: "Copenhagen",
|
||||||
|
country_code: "DK",
|
||||||
|
postal_code: "2100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ runValidators: true }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("throws on invalid email", async () => {
|
it("updates with password", async () => {
|
||||||
try {
|
await customerService.update(IdMap.getId("testCustomer"), {
|
||||||
await customerService.updateEmail(
|
password: "newpassword",
|
||||||
IdMap.getId("testCustomer"),
|
|
||||||
"olivermedusa"
|
|
||||||
)
|
|
||||||
} catch (err) {
|
|
||||||
expect(err.message).toEqual("The email is not valid")
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(CustomerModelMock.updateOne).toHaveBeenCalledTimes(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("updateBillingAddress", () => {
|
|
||||||
const customerService = new CustomerService({
|
|
||||||
customerModel: CustomerModelMock,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
expect(CustomerModelMock.updateOne).toBeCalledTimes(1)
|
||||||
jest.clearAllMocks()
|
expect(CustomerModelMock.updateOne).toBeCalledWith(
|
||||||
})
|
{ _id: IdMap.getId("testCustomer") },
|
||||||
|
{
|
||||||
it("successfully updates billing address", async () => {
|
$set: {
|
||||||
await customerService.updateBillingAddress(
|
has_account: true,
|
||||||
IdMap.getId("testCustomer"),
|
password_hash: expect.stringMatching(
|
||||||
{
|
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
|
||||||
first_name: "Olli",
|
),
|
||||||
last_name: "Juhl",
|
|
||||||
address_1: "Laksegade",
|
|
||||||
city: "Copenhagen",
|
|
||||||
country_code: "DK",
|
|
||||||
postal_code: "2100",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(CustomerModelMock.updateOne).toHaveBeenCalledTimes(1)
|
|
||||||
expect(CustomerModelMock.updateOne).toHaveBeenCalledWith(
|
|
||||||
{
|
|
||||||
_id: IdMap.getId("testCustomer"),
|
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
$set: {
|
{ runValidators: true }
|
||||||
billing_address: {
|
)
|
||||||
first_name: "Olli",
|
|
||||||
last_name: "Juhl",
|
|
||||||
address_1: "Laksegade",
|
|
||||||
city: "Copenhagen",
|
|
||||||
country_code: "DK",
|
|
||||||
postal_code: "2100",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("throws on invalid address", async () => {
|
|
||||||
try {
|
|
||||||
await customerService.updateBillingAddress(
|
|
||||||
IdMap.getId("testCustomer"),
|
|
||||||
{
|
|
||||||
first_name: 1234,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} catch (err) {
|
|
||||||
expect(err.message).toEqual("The address is not valid")
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(CustomerModelMock.updateOne).toHaveBeenCalledTimes(0)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("delete", () => {
|
describe("delete", () => {
|
||||||
const customerService = new CustomerService({
|
const customerService = new CustomerService({
|
||||||
customerModel: CustomerModelMock,
|
customerModel: CustomerModelMock,
|
||||||
|
|||||||
@@ -6,10 +6,14 @@ import { BaseService } from "medusa-interfaces"
|
|||||||
* @implements BaseService
|
* @implements BaseService
|
||||||
*/
|
*/
|
||||||
class AuthService extends BaseService {
|
class AuthService extends BaseService {
|
||||||
constructor({ userService }) {
|
constructor({ userService, customerService }) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
/** @private @const {UserService} */
|
/** @private @const {UserService} */
|
||||||
this.userService_ = userService
|
this.userService_ = userService
|
||||||
|
|
||||||
|
/** @private @const {CustomerService} */
|
||||||
|
this.customerService_ = customerService
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,6 +42,7 @@ class AuthService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticates a given user based on an email, password combination. Uses
|
* Authenticates a given user based on an email, password combination. Uses
|
||||||
* bcrypt to match password with hashed value.
|
* bcrypt to match password with hashed value.
|
||||||
@@ -70,6 +75,49 @@ class AuthService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticates a customer based on an email, password combination. Uses
|
||||||
|
* bcrypt to match password with hashed value.
|
||||||
|
* @param {string} email - the email of the user
|
||||||
|
* @param {string} password - the password of the user
|
||||||
|
* @return {{ success: (bool), user: (object | undefined) }}
|
||||||
|
* success: whether authentication succeeded
|
||||||
|
* user: the user document if authentication succeded
|
||||||
|
* error: a string with the error message
|
||||||
|
*/
|
||||||
|
async authenticateCustomer(email, password) {
|
||||||
|
try {
|
||||||
|
const customer = await this.customerService_.retrieveByEmail(email)
|
||||||
|
if (!customer.password_hash) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Invalid email or password",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordsMatch = await bcrypt.compare(
|
||||||
|
password,
|
||||||
|
customer.password_hash
|
||||||
|
)
|
||||||
|
if (passwordsMatch) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
customer,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Invalid email or password",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Invalid email or password",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AuthService
|
export default AuthService
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import mongoose from "mongoose"
|
import mongoose from "mongoose"
|
||||||
|
import bcrypt from "bcrypt"
|
||||||
import _ from "lodash"
|
import _ from "lodash"
|
||||||
import { Validator, MedusaError } from "medusa-core-utils"
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
import { BaseService } from "medusa-interfaces"
|
import { BaseService } from "medusa-interfaces"
|
||||||
@@ -68,6 +69,36 @@ class CustomerService extends BaseService {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a JSON Web token, that will be sent to a customer, that wishes to
|
||||||
|
* reset password.
|
||||||
|
* The token will be signed with the customer's current password hash as a
|
||||||
|
* secret a long side a payload with userId and the expiry time for the token,
|
||||||
|
* which is always 15 minutes.
|
||||||
|
* @param {string} customerId - the customer to reset the password for
|
||||||
|
* @returns {string} the generated JSON web token
|
||||||
|
*/
|
||||||
|
async generateResetPasswordToken(customerId) {
|
||||||
|
const customer = await this.retrieve(customerId)
|
||||||
|
|
||||||
|
if (!customer.has_account) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"You must have an account to reset the password. Create an account first"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const secret = customer.password_hash
|
||||||
|
const expiry = Math.floor(Date.now() / 1000) + 60 * 15 // 15 minutes ahead
|
||||||
|
const payload = { customer_id: customer._id, exp: expiry }
|
||||||
|
const token = jwt.sign(payload, secret)
|
||||||
|
|
||||||
|
// TODO: Call event layer to ensure that there is an email service that
|
||||||
|
// sends the token.
|
||||||
|
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} selector - the query object for find
|
* @param {Object} selector - the query object for find
|
||||||
* @return {Promise} the result of the find operation
|
* @return {Promise} the result of the find operation
|
||||||
@@ -99,63 +130,54 @@ class CustomerService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a customer with email and billing address
|
* Gets a customer by email.
|
||||||
* (if provided) being validated.
|
* @param {string} email - the email of the customer to get.
|
||||||
|
* @return {Promise<Customer>} the customer document.
|
||||||
|
*/
|
||||||
|
async retrieveByEmail(email) {
|
||||||
|
this.validateEmail_(email)
|
||||||
|
const customer = await this.customerModel_.findOne({ email }).catch(err => {
|
||||||
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!customer) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Customer with email ${email} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return customer
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a customer from an email - customers can have accounts associated,
|
||||||
|
* e.g. to login and view order history, etc. If a password is provided the
|
||||||
|
* customer will automatically get an account, otherwise the customer is just
|
||||||
|
* used to hold details of customers.
|
||||||
* @param {object} customer - the customer to create
|
* @param {object} customer - the customer to create
|
||||||
* @return {Promise} the result of create
|
* @return {Promise} the result of create
|
||||||
*/
|
*/
|
||||||
create(customer) {
|
async create(customer) {
|
||||||
const { email, billing_address } = customer
|
const { email, billing_address, password } = customer
|
||||||
this.validateEmail_(email)
|
this.validateEmail_(email)
|
||||||
|
|
||||||
if (billing_address) {
|
if (billing_address) {
|
||||||
this.validateBillingAddress_(billing_address)
|
this.validateBillingAddress_(billing_address)
|
||||||
}
|
}
|
||||||
this.customerModel_.create(customer).catch(err => {
|
|
||||||
|
if (password) {
|
||||||
|
const hashedPassword = await bcrypt.hash(password, 10)
|
||||||
|
customer.password_hash = hashedPassword
|
||||||
|
customer.has_account = true
|
||||||
|
delete customer.password
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.customerModel_.create(customer).catch(err => {
|
||||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the email of a customer
|
|
||||||
* @param {string} customerId - the id of the customer to update
|
|
||||||
* @param {string} email - the email to add to customer
|
|
||||||
* @return {Promise} the result of the update operation
|
|
||||||
*/
|
|
||||||
async updateEmail(customerId, email) {
|
|
||||||
const customer = await this.retrieve(customerId)
|
|
||||||
this.validateEmail_(email)
|
|
||||||
|
|
||||||
return this.customerModel_.updateOne(
|
|
||||||
{
|
|
||||||
_id: customer._id,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$set: { email },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the billing address of a customer
|
|
||||||
* @param {*} customerId - the customer to update address on
|
|
||||||
* @param {*} address - the new address to replace the current one
|
|
||||||
* @return {Promise} the result of the update operation
|
|
||||||
*/
|
|
||||||
async updateBillingAddress(customerId, address) {
|
|
||||||
const customer = await this.retrieve(customerId)
|
|
||||||
|
|
||||||
this.validateBillingAddress_(address)
|
|
||||||
|
|
||||||
return this.customerModel_.updateOne(
|
|
||||||
{
|
|
||||||
_id: customer._id,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$set: { billing_address: address },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a customer. Metadata updates and address updates should
|
* Updates a customer. Metadata updates and address updates should
|
||||||
* use dedicated methods, e.g. `setMetadata`, etc. The function
|
* use dedicated methods, e.g. `setMetadata`, etc. The function
|
||||||
@@ -175,11 +197,19 @@ class CustomerService extends BaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (update.email) {
|
||||||
|
this.validateEmail_(update.email)
|
||||||
|
}
|
||||||
|
|
||||||
if (update.billing_address) {
|
if (update.billing_address) {
|
||||||
throw new MedusaError(
|
this.validateBillingAddress_(update.billing_address)
|
||||||
MedusaError.Types.INVALID_DATA,
|
}
|
||||||
"Use updateBillingAddress to update billing address"
|
|
||||||
)
|
if (update.password) {
|
||||||
|
const hashedPassword = await bcrypt.hash(update.password, 10)
|
||||||
|
update.password_hash = hashedPassword
|
||||||
|
update.has_account = true
|
||||||
|
delete update.password
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.customerModel_
|
return this.customerModel_
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ class UserService extends BaseService {
|
|||||||
const user = await this.retrieve(userId)
|
const user = await this.retrieve(userId)
|
||||||
const secret = user.password_hash
|
const secret = user.password_hash
|
||||||
const expiry = Math.floor(Date.now() / 1000) + 60 * 15
|
const expiry = Math.floor(Date.now() / 1000) + 60 * 15
|
||||||
const payload = { userId: user._id, exp: expiry }
|
const payload = { user_id: user._id, exp: expiry }
|
||||||
const token = jwt.sign(payload, secret)
|
const token = jwt.sign(payload, secret)
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user