Adds Oauth support to plugins
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
application_name: Validator.string().required(),
|
||||
state: Validator.string().required(),
|
||||
code: Validator.string().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
try {
|
||||
const oauthService = req.scope.resolve("oauthService")
|
||||
const data = await oauthService.generateToken(
|
||||
value.application_name,
|
||||
value.code,
|
||||
value.state
|
||||
)
|
||||
res.status(200).json({ apps: data })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Router } from "express"
|
||||
import middlewares from "../../../middlewares"
|
||||
|
||||
const route = Router()
|
||||
|
||||
export default app => {
|
||||
app.use("/apps", route)
|
||||
|
||||
route.get("/", middlewares.wrap(require("./list").default))
|
||||
route.post(
|
||||
"/authorizations",
|
||||
middlewares.wrap(require("./authorize-app").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
try {
|
||||
const oauthService = req.scope.resolve("oauthService")
|
||||
const data = await oauthService.list({})
|
||||
|
||||
res.status(200).json({ apps: data })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import orderRoutes from "./orders"
|
||||
import storeRoutes from "./store"
|
||||
import uploadRoutes from "./uploads"
|
||||
import customerRoutes from "./customers"
|
||||
import appRoutes from "./apps"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -40,6 +41,7 @@ export default (app, container, config) => {
|
||||
// Calls all middleware that has been registered to run after authentication.
|
||||
middlewareService.usePostAuthentication(app)
|
||||
|
||||
appRoutes(route)
|
||||
productRoutes(route)
|
||||
userRoutes(route)
|
||||
regionRoutes(route)
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
PaymentService,
|
||||
FulfillmentService,
|
||||
FileService,
|
||||
OauthService,
|
||||
} from "medusa-interfaces"
|
||||
import { getConfigFile, createRequireFromPath } from "medusa-core-utils"
|
||||
import _ from "lodash"
|
||||
@@ -47,14 +48,16 @@ export default async ({ rootDirectory, container, app }) => {
|
||||
version: createFileContentHash(process.cwd(), `**`),
|
||||
})
|
||||
|
||||
resolved.forEach(pluginDetails => {
|
||||
registerModels(pluginDetails, container)
|
||||
registerServices(pluginDetails, container)
|
||||
registerMedusaApi(pluginDetails, container)
|
||||
registerApi(pluginDetails, app)
|
||||
registerCoreRouters(pluginDetails, container)
|
||||
registerSubscribers(pluginDetails, container)
|
||||
})
|
||||
await Promise.all(
|
||||
resolved.map(async pluginDetails => {
|
||||
registerModels(pluginDetails, container)
|
||||
await registerServices(pluginDetails, container)
|
||||
registerMedusaApi(pluginDetails, container)
|
||||
registerApi(pluginDetails, app)
|
||||
registerCoreRouters(pluginDetails, container)
|
||||
registerSubscribers(pluginDetails, container)
|
||||
})
|
||||
)
|
||||
|
||||
await Promise.all(
|
||||
resolved.map(async pluginDetails => runLoaders(pluginDetails, container))
|
||||
@@ -156,58 +159,80 @@ function registerApi(pluginDetails, app) {
|
||||
* registered
|
||||
* @return {void}
|
||||
*/
|
||||
function registerServices(pluginDetails, container) {
|
||||
async function registerServices(pluginDetails, container) {
|
||||
const files = glob.sync(`${pluginDetails.resolve}/services/[!__]*`, {})
|
||||
files.forEach(fn => {
|
||||
const loaded = require(fn).default
|
||||
const name = formatRegistrationName(fn)
|
||||
await Promise.all(
|
||||
files.map(async fn => {
|
||||
const loaded = require(fn).default
|
||||
const name = formatRegistrationName(fn)
|
||||
|
||||
if (!(loaded.prototype instanceof BaseService)) {
|
||||
const logger = container.resolve("logger")
|
||||
const message = `Services must inherit from BaseService, please check ${fn}`
|
||||
logger.error(message)
|
||||
throw new Error(message)
|
||||
}
|
||||
if (!(loaded.prototype instanceof BaseService)) {
|
||||
const logger = container.resolve("logger")
|
||||
const message = `Services must inherit from BaseService, please check ${fn}`
|
||||
logger.error(message)
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
if (loaded.prototype instanceof PaymentService) {
|
||||
// Register our payment providers to paymentProviders
|
||||
container.registerAdd(
|
||||
"paymentProviders",
|
||||
asFunction(cradle => new loaded(cradle, pluginDetails.options))
|
||||
)
|
||||
if (loaded.prototype instanceof PaymentService) {
|
||||
// Register our payment providers to paymentProviders
|
||||
container.registerAdd(
|
||||
"paymentProviders",
|
||||
asFunction(cradle => new loaded(cradle, pluginDetails.options))
|
||||
)
|
||||
|
||||
// Add the service directly to the container in order to make simple
|
||||
// resolution if we already know which payment provider we need to use
|
||||
container.register({
|
||||
[name]: asFunction(cradle => new loaded(cradle, pluginDetails.options)),
|
||||
[`pp_${loaded.identifier}`]: aliasTo(name),
|
||||
})
|
||||
} else if (loaded.prototype instanceof FulfillmentService) {
|
||||
// Register our payment providers to paymentProviders
|
||||
container.registerAdd(
|
||||
"fulfillmentProviders",
|
||||
asFunction(cradle => new loaded(cradle, pluginDetails.options))
|
||||
)
|
||||
// Add the service directly to the container in order to make simple
|
||||
// resolution if we already know which payment provider we need to use
|
||||
container.register({
|
||||
[name]: asFunction(
|
||||
cradle => new loaded(cradle, pluginDetails.options)
|
||||
),
|
||||
[`pp_${loaded.identifier}`]: aliasTo(name),
|
||||
})
|
||||
} else if (loaded.prototype instanceof OauthService) {
|
||||
const oauthService = container.resolve("oauthService")
|
||||
|
||||
// Add the service directly to the container in order to make simple
|
||||
// resolution if we already know which payment provider we need to use
|
||||
container.register({
|
||||
[name]: asFunction(cradle => new loaded(cradle, pluginDetails.options)),
|
||||
[`fp_${loaded.identifier}`]: aliasTo(name),
|
||||
})
|
||||
} else if (loaded.prototype instanceof FileService) {
|
||||
// Add the service directly to the container in order to make simple
|
||||
// resolution if we already know which payment provider we need to use
|
||||
container.register({
|
||||
[name]: asFunction(cradle => new loaded(cradle, pluginDetails.options)),
|
||||
[`fileService`]: aliasTo(name),
|
||||
})
|
||||
} else {
|
||||
container.register({
|
||||
[name]: asFunction(cradle => new loaded(cradle, pluginDetails.options)),
|
||||
})
|
||||
}
|
||||
})
|
||||
const appDetails = loaded.getAppDetails(pluginDetails.options)
|
||||
await oauthService.registerOauthApp(appDetails)
|
||||
|
||||
const name = appDetails.application_name
|
||||
container.register({
|
||||
[`${name}Oauth`]: asFunction(
|
||||
cradle => new loaded(cradle, pluginDetails.options)
|
||||
),
|
||||
})
|
||||
} else if (loaded.prototype instanceof FulfillmentService) {
|
||||
// Register our payment providers to paymentProviders
|
||||
container.registerAdd(
|
||||
"fulfillmentProviders",
|
||||
asFunction(cradle => new loaded(cradle, pluginDetails.options))
|
||||
)
|
||||
|
||||
// Add the service directly to the container in order to make simple
|
||||
// resolution if we already know which payment provider we need to use
|
||||
container.register({
|
||||
[name]: asFunction(
|
||||
cradle => new loaded(cradle, pluginDetails.options)
|
||||
),
|
||||
[`fp_${loaded.identifier}`]: aliasTo(name),
|
||||
})
|
||||
} else if (loaded.prototype instanceof FileService) {
|
||||
// Add the service directly to the container in order to make simple
|
||||
// resolution if we already know which payment provider we need to use
|
||||
container.register({
|
||||
[name]: asFunction(
|
||||
cradle => new loaded(cradle, pluginDetails.options)
|
||||
),
|
||||
[`fileService`]: aliasTo(name),
|
||||
})
|
||||
} else {
|
||||
container.register({
|
||||
[name]: asFunction(
|
||||
cradle => new loaded(cradle, pluginDetails.options)
|
||||
),
|
||||
})
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import mongoose from "mongoose"
|
||||
import { BaseModel } from "medusa-interfaces"
|
||||
|
||||
class OauthModel extends BaseModel {
|
||||
static modelName = "Oauth"
|
||||
|
||||
static schema = {
|
||||
display_name: { type: String, required: true },
|
||||
application_name: { type: String, required: true, unique: true },
|
||||
install_url: { type: String, required: true },
|
||||
uninstall_url: { type: String, default: "" },
|
||||
data: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||
}
|
||||
}
|
||||
|
||||
export default OauthModel
|
||||
@@ -0,0 +1,110 @@
|
||||
import _ from "lodash"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { OauthService } from "medusa-interfaces"
|
||||
|
||||
class Oauth extends OauthService {
|
||||
static Events = {
|
||||
TOKEN_GENERATED: "oauth.token_generated",
|
||||
TOKEN_REFRESHED: "oauth.token_refreshed",
|
||||
}
|
||||
|
||||
constructor(cradle) {
|
||||
super()
|
||||
this.container_ = cradle
|
||||
this.model_ = cradle.oauthModel
|
||||
this.eventBus_ = cradle.eventBusService
|
||||
}
|
||||
|
||||
retrieveByName(appName) {
|
||||
return this.model_.findOne({
|
||||
application_name: appName,
|
||||
})
|
||||
}
|
||||
|
||||
list(selector) {
|
||||
return this.model_.find(selector)
|
||||
}
|
||||
|
||||
create(data) {
|
||||
return this.model_.create({
|
||||
display_name: data.display_name,
|
||||
application_name: data.application_name,
|
||||
install_url: data.install_url,
|
||||
uninstall_url: data.uninstall_url,
|
||||
})
|
||||
}
|
||||
|
||||
update(id, update) {
|
||||
return this.model_.updateOne(
|
||||
{
|
||||
_id: id,
|
||||
},
|
||||
update
|
||||
)
|
||||
}
|
||||
|
||||
async registerOauthApp(appDetails) {
|
||||
const { application_name } = appDetails
|
||||
const existing = await this.retrieveByName(application_name)
|
||||
if (existing) {
|
||||
return
|
||||
}
|
||||
|
||||
return this.create(appDetails)
|
||||
}
|
||||
|
||||
async generateToken(appName, code, state) {
|
||||
const app = await this.retrieveByName(appName)
|
||||
const service = this.container_[`${app.application_name}Oauth`]
|
||||
if (!service) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`An OAuth handler for ${app.display_name} could not be found make sure the plugin is installed`
|
||||
)
|
||||
}
|
||||
|
||||
if (!app.state === state) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`${app.display_name} could not match state`
|
||||
)
|
||||
}
|
||||
|
||||
const authData = await service.generateToken(code)
|
||||
|
||||
return this.update(app._id, {
|
||||
data: authData,
|
||||
}).then(result => {
|
||||
this.eventBus_.emit(
|
||||
`${Oauth.Events.TOKEN_GENERATED}.${appName}`,
|
||||
authData
|
||||
)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
async refreshToken(appName, refreshToken) {
|
||||
const app = await this.retrieveByName(appName)
|
||||
const service = this.container_[`${app.application_name}Oauth`]
|
||||
if (!service) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`An OAuth handler for ${app.display_name} could not be found make sure the plugin is installed`
|
||||
)
|
||||
}
|
||||
|
||||
const authData = await service.refreshToken(refreshToken)
|
||||
|
||||
return this.update(app._id, {
|
||||
data: authData,
|
||||
}).then(result => {
|
||||
this.eventBus_.emit(
|
||||
`${Oauth.Events.TOKEN_REFRESHED}.${appName}`,
|
||||
authData
|
||||
)
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Oauth
|
||||
@@ -4568,14 +4568,6 @@ media-typer@0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||
|
||||
medusa-core-utils@^0.3.0:
|
||||
version "0.1.39"
|
||||
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-0.1.39.tgz#d57816c9bd43f9a92883650c1e66add1665291df"
|
||||
integrity sha512-R8+U1ile7if+nR6Cjh5exunx0ETV0OfkWUUBUpz1KmHSDv0V0CcvQqU9lcZesPFDEbu3Y2iEjsCqidVA4nG2nQ==
|
||||
dependencies:
|
||||
"@hapi/joi" "^16.1.8"
|
||||
joi-objectid "^3.0.1"
|
||||
|
||||
medusa-interfaces@^0.1.27:
|
||||
version "0.1.27"
|
||||
resolved "https://registry.yarnpkg.com/medusa-interfaces/-/medusa-interfaces-0.1.27.tgz#e77f9a9f82a7118eac8b35c1498ef8a5cec78898"
|
||||
@@ -4583,13 +4575,6 @@ medusa-interfaces@^0.1.27:
|
||||
dependencies:
|
||||
mongoose "^5.8.0"
|
||||
|
||||
medusa-test-utils@^0.3.0:
|
||||
version "0.1.39"
|
||||
resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-0.1.39.tgz#b7c166006a2fa4f02e52ab3bfafc19a3ae787f3e"
|
||||
integrity sha512-M/Br8/HYvl7x2oLnme4NxdQwoyV0XUyOWiCyvPp7q1HUTB684lhJf1MikZVrcSjsh2L1rpyi3GRbKdf4cpJWvw==
|
||||
dependencies:
|
||||
mongoose "^5.8.0"
|
||||
|
||||
memory-pager@^1.0.2:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"
|
||||
|
||||
Reference in New Issue
Block a user