Adds Oauth support to plugins

This commit is contained in:
Sebastian Rindom
2020-08-04 17:13:47 +02:00
parent e69c3aba01
commit 21bc096b2e
21 changed files with 688 additions and 685 deletions
@@ -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)
+80 -55
View File
@@ -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)
),
})
}
})
)
}
/**
+16
View File
@@ -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
+110
View File
@@ -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
-15
View File
@@ -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"