chore(): Reorganize modules (#7210)

**What**
Move all modules to the modules directory
This commit is contained in:
Adrien de Peretti
2024-05-02 17:33:34 +02:00
committed by GitHub
parent 7a351eef09
commit 4eae25e1ef
870 changed files with 91 additions and 62 deletions

6
packages/modules/auth/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/dist
node_modules
.DS_store
.env*
.env
*.sql

View File

@@ -0,0 +1,25 @@
# @medusajs/auth
## 0.0.3
### Patch Changes
- [#6700](https://github.com/medusajs/medusa/pull/6700) [`8f8a4f9b13`](https://github.com/medusajs/medusa/commit/8f8a4f9b1353087d98f6cc75346d43a7f49901a8) Thanks [@olivermrbl](https://github.com/olivermrbl)! - chore: Version all modules to allow for initial testing
- Updated dependencies [[`1fd0457c15`](https://github.com/medusajs/medusa/commit/1fd0457c153b2ef7657c052878d8e5364e1b324a), [`9288f53327`](https://github.com/medusajs/medusa/commit/9288f53327b8ce617af92ed8d14d9459cbfeb13c), [`d4b921f3db`](https://github.com/medusajs/medusa/commit/d4b921f3dbe0a38f1565a8de759996c70798d58e), [`ac86362e81`](https://github.com/medusajs/medusa/commit/ac86362e81d8523cb8e3dfad026fc94658513018), [`e4acde1aa2`](https://github.com/medusajs/medusa/commit/e4acde1aa2eb57f07e6692fe8b61f728948b9a96), [`1a661adf3e`](https://github.com/medusajs/medusa/commit/1a661adf3ef4991aa6e237dd894b6a5c47cd4aca), [`56cbf88115`](https://github.com/medusajs/medusa/commit/56cbf88115994adea7037c3f2814f0c96af3cfc0), [`36a61658f9`](https://github.com/medusajs/medusa/commit/36a61658f969a7b19c84a1e621ad1464927cafb1), [`04a532e5ef`](https://github.com/medusajs/medusa/commit/04a532e5efabbf75b1e4155520b1da175b686ffc), [`c319edb8e0`](https://github.com/medusajs/medusa/commit/c319edb8e0ecd13d086652147667916e5abab2d8), [`0b9fcb6324`](https://github.com/medusajs/medusa/commit/0b9fcb6324eee9f2556c7e6317775fae93b12a47), [`586df9da25`](https://github.com/medusajs/medusa/commit/586df9da250e492442769f5bac2f8b3de1d46f05), [`b3d826497b`](https://github.com/medusajs/medusa/commit/b3d826497b3dae5e1b26b7924706c24fd5e87ca5), [`a86c87fe14`](https://github.com/medusajs/medusa/commit/a86c87fe1442afce9285e39255914e01012b4449), [`640eccd5dd`](https://github.com/medusajs/medusa/commit/640eccd5ddbb163e0f987ce6c772f1129c2e2632), [`8ea37d03c9`](https://github.com/medusajs/medusa/commit/8ea37d03c914a5004a3e42770668b2d1f7f8f564), [`339a946f38`](https://github.com/medusajs/medusa/commit/339a946f389033c21e05338f9dbf07d88e140533), [`ac829fc67f`](https://github.com/medusajs/medusa/commit/ac829fc67f7495b08f28e55923c59f0fd6320311), [`d9d5afc3cf`](https://github.com/medusajs/medusa/commit/d9d5afc3cfc29221d0e65bff7b78474a8fb8f31f), [`c3c4f49fc2`](https://github.com/medusajs/medusa/commit/c3c4f49fc2126f950e69e291ca939ca88a15afd3), [`9288f53327`](https://github.com/medusajs/medusa/commit/9288f53327b8ce617af92ed8d14d9459cbfeb13c), [`0d46abf0ff`](https://github.com/medusajs/medusa/commit/0d46abf0ffa4c5e03bf7d2a9cdf1db828a76bea8), [`fafde4f54d`](https://github.com/medusajs/medusa/commit/fafde4f54d3ef75a7d382e6cbf94e38b3deae99b), [`8dad2b51a2`](https://github.com/medusajs/medusa/commit/8dad2b51a26c4c3c14a6c95f70424c8bef2ad63e), [`0c705d7bd4`](https://github.com/medusajs/medusa/commit/0c705d7bd41a768c48017ae95b3c8414d96c6acb), [`a6d7070dd6`](https://github.com/medusajs/medusa/commit/a6d7070dd669c21ea19d70434d42c2f8167dc309), [`1d91b7429b`](https://github.com/medusajs/medusa/commit/1d91b7429beebd6f09d5027f7f7e1fe74ce3a8ff), [`168f02f138`](https://github.com/medusajs/medusa/commit/168f02f138ad101e1013f2c8c3f8dc19de12accf), [`1ed5f918c3`](https://github.com/medusajs/medusa/commit/1ed5f918c31794a70aca4a4e4cd83cf456593baa), [`c20eb15cd9`](https://github.com/medusajs/medusa/commit/c20eb15cd9b1bd90c8d01f68eca6f0f181cd902d), [`e5945479e0`](https://github.com/medusajs/medusa/commit/e5945479e091d9560ae3e7240306a31031ef4584), [`f5c2256286`](https://github.com/medusajs/medusa/commit/f5c22562867f412040f8bc6c55ab5de3a3735e62), [`000eb61e33`](https://github.com/medusajs/medusa/commit/000eb61e33e0302db95ee6ad1656ea9b430ed471), [`d550be3685`](https://github.com/medusajs/medusa/commit/d550be3685423218d47a20c57a5e06758f4a961a), [`62a7bcc30c`](https://github.com/medusajs/medusa/commit/62a7bcc30cbc7b234b2b51d7858439951a84edeb), [`8f8a4f9b13`](https://github.com/medusajs/medusa/commit/8f8a4f9b1353087d98f6cc75346d43a7f49901a8), [`6500f18b9b`](https://github.com/medusajs/medusa/commit/6500f18b9b80c5c9c473489e7e740d55dca74303), [`ce39b9b66e`](https://github.com/medusajs/medusa/commit/ce39b9b66e8c277ec0691ea6d0a950003be09cc1), [`a6a4b3f01a`](https://github.com/medusajs/medusa/commit/a6a4b3f01a6d2bd97b1580c59134279a1b033a5d), [`4d51f095b3`](https://github.com/medusajs/medusa/commit/4d51f095b3f98f468cefb760512563f7b77bb9cf), [`4625bd1241`](https://github.com/medusajs/medusa/commit/4625bd12416275b09c22cde4a09cb0f68df5d7c1), [`56b0b45304`](https://github.com/medusajs/medusa/commit/56b0b4530401a6ec5aa155874d371e45bb388fe2), [`cc1b66842c`](https://github.com/medusajs/medusa/commit/cc1b66842cbb37c6eab84e2d8b74844c214f38d7), [`24fb102a56`](https://github.com/medusajs/medusa/commit/24fb102a564b1253d1f8b039bb1e435cc5312fbb), [`e85463b2a7`](https://github.com/medusajs/medusa/commit/e85463b2a717751de2e21c39a4c745449b31affe)]:
- @medusajs/types@1.11.14
- @medusajs/utils@1.11.7
- @medusajs/modules-sdk@1.12.9
## 0.0.2
### Patch Changes
- [#6062](https://github.com/medusajs/medusa/pull/6062) [`72bc52231c`](https://github.com/medusajs/medusa/commit/72bc52231ca3a72fa6d197a248fe07a938ed0d85) Thanks [@adrien2p](https://github.com/adrien2p)! - chore(utils): Update base repository to infer primary keys and support composite
- [#6035](https://github.com/medusajs/medusa/pull/6035) [`b6ac768698`](https://github.com/medusajs/medusa/commit/b6ac768698a3b49d0162cb49e628386f3352d034) Thanks [@adrien2p](https://github.com/adrien2p)! - chore: Attempt to abstract the modules repository
- Updated dependencies [[`68ddd866a5`](https://github.com/medusajs/medusa/commit/68ddd866a5ff9414e2db5b80d75acc5e81948540), [`72bc52231c`](https://github.com/medusajs/medusa/commit/72bc52231ca3a72fa6d197a248fe07a938ed0d85), [`99045848f`](https://github.com/medusajs/medusa/commit/99045848fd3e863359c7878d9bc05271ed083a0e), [`af7af7374`](https://github.com/medusajs/medusa/commit/af7af737455daa0f330840a9678e6339e519dfe6), [`fc6b1772a7`](https://github.com/medusajs/medusa/commit/fc6b1772a71582bb48602c5cac7b2297e9d267a9), [`a9b4214503`](https://github.com/medusajs/medusa/commit/a9b42145032ee88aa922a11fe03e777b140c68f4), [`d85fee42e`](https://github.com/medusajs/medusa/commit/d85fee42ee7f661310584dfee5741d6c53b989bb), [`5e655dd59`](https://github.com/medusajs/medusa/commit/5e655dd59bda4ffface28db38021ba71cae6de10), [`b132ff7669`](https://github.com/medusajs/medusa/commit/b132ff76693148b3a06373c168e8dd5e02970757), [`e28fa7fbdf`](https://github.com/medusajs/medusa/commit/e28fa7fbdf45c5b1fa19848db731132a0bf1757d), [`a12c28b7d5`](https://github.com/medusajs/medusa/commit/a12c28b7d5faed733bebbb4963dff50b9c8a33bc), [`b782d3bcb7`](https://github.com/medusajs/medusa/commit/b782d3bcb7e8088a962584b9a55200dd29c2161c), [`2b9f98895e`](https://github.com/medusajs/medusa/commit/2b9f98895eaca255e01278674b11cd7cb69b388f), [`7f7cb2a263`](https://github.com/medusajs/medusa/commit/7f7cb2a263c26baf540b05a40ab3732ffeb0c73c), [`302323916`](https://github.com/medusajs/medusa/commit/302323916b6d8eaf571cd59b5fc92a913af207de), [`da5cc4cf7`](https://github.com/medusajs/medusa/commit/da5cc4cf7f7f0ef40d409704a95b025ce95477f4), [`daecd82a7`](https://github.com/medusajs/medusa/commit/daecd82a7cdf7315599f464999690414c20d6748), [`ce81cade88`](https://github.com/medusajs/medusa/commit/ce81cade887659cefe9638e3c1c2807378191c62), [`fd78f5e24`](https://github.com/medusajs/medusa/commit/fd78f5e24263f5e158c3b7d11fbf0a4436e9c17a), [`192bc336cc`](https://github.com/medusajs/medusa/commit/192bc336cc2b6ec3820d94524c046dcd3c4ac7d9), [`06b33a9b4`](https://github.com/medusajs/medusa/commit/06b33a9b4525b77b1b14b35b973209700945654e), [`b6ac768698`](https://github.com/medusajs/medusa/commit/b6ac768698a3b49d0162cb49e628386f3352d034), [`130c641e5c`](https://github.com/medusajs/medusa/commit/130c641e5c91cf831de64fb87aebbfdc4d23530d), [`fade8ea7bf`](https://github.com/medusajs/medusa/commit/fade8ea7bf560343ecbde116d226ac44053cdb8e), [`8472460f53`](https://github.com/medusajs/medusa/commit/8472460f533322cc4535199aa768ac163021bc79), [`68d8daccd`](https://github.com/medusajs/medusa/commit/68d8daccd2a8508a13e211130e49017198b51fab)]:
- @medusajs/types@1.11.11
- @medusajs/utils@1.11.4
- @medusajs/modules-sdk@1.12.7

View File

@@ -0,0 +1,3 @@
# Auth Module
The Auth Module is Medusas authentication engine engine. It provides functions to authenticate users through identity providers and store metadata about users that can be used for authorization purposes.

View File

@@ -0,0 +1,37 @@
import { AuthUser } from "@models"
import { SqlEntityManager } from "@mikro-orm/postgresql"
export async function createAuthUsers(
manager: SqlEntityManager,
userData: any[] = [
{
id: "test-id",
entity_id: "test-id",
provider: "manual",
scope: "store",
},
{
id: "test-id-1",
entity_id: "test-id-1",
provider: "manual",
scope: "store",
},
{
entity_id: "test-id-2",
provider: "store",
scope: "store",
},
]
): Promise<AuthUser[]> {
const authUsers: AuthUser[] = []
for (const user of userData) {
const authUser = manager.create(AuthUser, user)
authUsers.push(authUser)
}
await manager.persistAndFlush(authUsers)
return authUsers
}

View File

@@ -0,0 +1,231 @@
import { createAuthUsers } from "../../../__fixtures__/auth-user"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { Modules } from "@medusajs/modules-sdk"
import { IAuthModuleService } from "@medusajs/types"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.AUTH,
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IAuthModuleService>) => {
describe("AuthUser Service", () => {
beforeEach(async () => {
await createAuthUsers(MikroOrmWrapper.forkManager())
})
describe("list", () => {
it("should list authUsers", async () => {
const authUsers = await service.list()
const serialized = JSON.parse(JSON.stringify(authUsers))
expect(serialized).toEqual([
expect.objectContaining({
provider: "store",
}),
expect.objectContaining({
provider: "manual",
}),
expect.objectContaining({
provider: "manual",
}),
])
})
it("should list authUsers by id", async () => {
const authUsers = await service.list({
id: ["test-id"],
})
expect(authUsers).toEqual([
expect.objectContaining({
id: "test-id",
}),
])
})
it("should list authUsers by provider_id", async () => {
const authUsers = await service.list({
provider: "manual",
})
const serialized = JSON.parse(JSON.stringify(authUsers))
expect(serialized).toEqual([
expect.objectContaining({
id: "test-id",
}),
expect.objectContaining({
id: "test-id-1",
}),
])
})
})
describe("listAndCount", () => {
it("should list authUsers", async () => {
const [authUsers, count] = await service.listAndCount()
const serialized = JSON.parse(JSON.stringify(authUsers))
expect(count).toEqual(3)
expect(serialized).toEqual([
expect.objectContaining({
provider: "store",
}),
expect.objectContaining({
provider: "manual",
}),
expect.objectContaining({
provider: "manual",
}),
])
})
it("should listAndCount authUsers by provider_id", async () => {
const [authUsers, count] = await service.listAndCount({
provider: "manual",
})
expect(count).toEqual(2)
expect(authUsers).toEqual([
expect.objectContaining({
id: "test-id",
}),
expect.objectContaining({
id: "test-id-1",
}),
])
})
})
describe("retrieve", () => {
const id = "test-id"
it("should return an authUser for the given id", async () => {
const authUser = await service.retrieve(id)
expect(authUser).toEqual(
expect.objectContaining({
id,
})
)
})
it("should return authUser based on config select param", async () => {
const authUser = await service.retrieve(id, {
select: ["id"],
})
const serialized = JSON.parse(JSON.stringify(authUser))
expect(serialized).toEqual({
id,
})
})
it("should throw an error when an authUser with the given id does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
"AuthUser with id: does-not-exist was not found"
)
})
it("should throw an error when a authUserId is not provided", async () => {
let error
try {
await service.retrieve(undefined as unknown as string)
} catch (e) {
error = e
}
expect(error.message).toEqual("authUser - id must be defined")
})
})
describe("delete", () => {
it("should delete the authUsers given an id successfully", async () => {
const id = "test-id"
await service.delete([id])
const authUsers = await service.list({
id: [id],
})
expect(authUsers).toHaveLength(0)
})
})
describe("update", () => {
it("should throw an error when a id does not exist", async () => {
let error
try {
await service.update([
{
id: "does-not-exist",
},
])
} catch (e) {
error = e
}
expect(error.message).toEqual(
'AuthUser with id "does-not-exist" not found'
)
})
it("should update authUser", async () => {
const id = "test-id"
await service.update([
{
id,
provider_metadata: { email: "test@email.com" },
},
])
const [authUser] = await service.list({ id: [id] })
expect(authUser).toEqual(
expect.objectContaining({
provider_metadata: { email: "test@email.com" },
})
)
})
})
describe("create", () => {
it("should create a authUser successfully", async () => {
await service.create([
{
id: "test",
provider: "manual",
entity_id: "test",
scope: "store",
},
])
const [authUser] = await service.list({
id: ["test"],
})
expect(authUser).toEqual(
expect.objectContaining({
id: "test",
})
)
})
})
})
},
})

View File

@@ -0,0 +1,237 @@
import { IAuthModuleService } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
import { createAuthUsers } from "../../../__fixtures__/auth-user"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.AUTH,
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IAuthModuleService>) => {
describe("AuthModuleService - AuthUser", () => {
beforeEach(async () => {
await createAuthUsers(MikroOrmWrapper.forkManager())
})
describe("listAuthUsers", () => {
it("should list authUsers", async () => {
const authUsers = await service.list()
expect(authUsers).toEqual([
expect.objectContaining({
provider: "store",
}),
expect.objectContaining({
provider: "manual",
}),
expect.objectContaining({
provider: "manual",
}),
])
})
it("should list authUsers by id", async () => {
const authUsers = await service.list({
id: ["test-id"],
})
expect(authUsers).toEqual([
expect.objectContaining({
id: "test-id",
}),
])
})
it("should list authUsers by provider", async () => {
const authUsers = await service.list({
provider: "manual",
})
expect(authUsers).toEqual([
expect.objectContaining({
id: "test-id",
}),
expect.objectContaining({
id: "test-id-1",
}),
])
})
})
describe("listAndCountAuthUsers", () => {
it("should list and count authUsers", async () => {
const [authUsers, count] = await service.listAndCount()
expect(count).toEqual(3)
expect(authUsers).toEqual([
expect.objectContaining({
provider: "store",
}),
expect.objectContaining({
provider: "manual",
}),
expect.objectContaining({
provider: "manual",
}),
])
})
it("should listAndCount authUsers by provider_id", async () => {
const [authUsers, count] = await service.listAndCount({
provider: "manual",
})
expect(count).toEqual(2)
expect(authUsers).toEqual([
expect.objectContaining({
id: "test-id",
}),
expect.objectContaining({
id: "test-id-1",
}),
])
})
})
describe("retrieveAuthUser", () => {
const id = "test-id"
it("should return an authUser for the given id", async () => {
const authUser = await service.retrieve(id)
expect(authUser).toEqual(
expect.objectContaining({
id,
})
)
})
it("should throw an error when an authUser with the given id does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
"AuthUser with id: does-not-exist was not found"
)
})
it("should not return an authUser with password hash", async () => {
const authUser = await service.retrieve("test-id-1")
expect(authUser).toEqual(
expect.objectContaining({
id: "test-id-1",
})
)
expect(authUser["password_hash"]).toEqual(undefined)
})
it("should throw an error when a authUserId is not provided", async () => {
let error
try {
await service.retrieve(undefined as unknown as string)
} catch (e) {
error = e
}
expect(error.message).toEqual("authUser - id must be defined")
})
it("should return authUser based on config select param", async () => {
const authUser = await service.retrieve(id, {
select: ["id"],
})
expect(authUser).toEqual({
id,
})
})
})
describe("deleteAuthUser", () => {
const id = "test-id"
it("should delete the authUsers given an id successfully", async () => {
await service.delete([id])
const authUsers = await service.list({
id: [id],
})
expect(authUsers).toHaveLength(0)
})
})
describe("updateAuthUser", () => {
const id = "test-id"
it("should throw an error when a id does not exist", async () => {
let error
try {
await service.update([
{
id: "does-not-exist",
},
])
} catch (e) {
error = e
}
expect(error.message).toEqual(
'AuthUser with id "does-not-exist" not found'
)
})
it("should update authUser", async () => {
await service.update([
{
id,
provider_metadata: { email: "test@email.com" },
},
])
const [authUser] = await service.list({ id: [id] })
expect(authUser).toEqual(
expect.objectContaining({
provider_metadata: { email: "test@email.com" },
})
)
})
})
describe("createAuthUser", () => {
it("should create a authUser successfully", async () => {
await service.create([
{
id: "test",
provider: "manual",
entity_id: "test",
scope: "store",
},
])
const [authUser, count] = await service.listAndCount({
id: ["test"],
})
expect(count).toEqual(1)
expect(authUser[0]).toEqual(
expect.objectContaining({
id: "test",
})
)
})
})
})
},
})

View File

@@ -0,0 +1,41 @@
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
import { IAuthModuleService } from "@medusajs/types"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.AUTH,
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IAuthModuleService>) => {
describe("AuthModuleService - AuthProvider", () => {
describe("authenticate", () => {
it("authenticate validates that a provider is registered in container", async () => {
const { success, error } = await service.authenticate(
"notRegistered",
{} as any
)
expect(success).toBe(false)
expect(error).toEqual(
"AuthenticationProvider: notRegistered wasn't registered in the module. Have you configured your options correctly?"
)
})
it("fails to authenticate using a valid provider with an invalid scope", async () => {
const { success, error } = await service.authenticate("emailpass", {
authScope: "non-existing",
} as any)
expect(success).toBe(false)
expect(error).toEqual(
`Scope "non-existing" is not valid for provider emailpass`
)
})
})
})
},
})

View File

@@ -0,0 +1,133 @@
import { MedusaModule, Modules } from "@medusajs/modules-sdk"
import { IAuthModuleService } from "@medusajs/types"
import Scrypt from "scrypt-kdf"
import { createAuthUsers } from "../../../__fixtures__/auth-user"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(30000)
const seedDefaultData = async (manager) => {
await createAuthUsers(manager)
}
moduleIntegrationTestRunner({
moduleName: Modules.AUTH,
moduleOptions: {
providers: [
{
name: "emailpass",
scopes: {
admin: {},
store: {},
},
},
],
},
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IAuthModuleService>) => {
describe("AuthModuleService - AuthProvider", () => {
describe("authenticate", () => {
it("authenticate validates that a provider is registered in container", async () => {
const password = "supersecret"
const email = "test@test.com"
const passwordHash = (
await Scrypt.kdf(password, { logN: 15, r: 8, p: 1 })
).toString("base64")
await seedDefaultData(MikroOrmWrapper.forkManager())
await createAuthUsers(MikroOrmWrapper.forkManager(), [
// Add authenticated user
{
provider: "emailpass",
entity_id: email,
scope: "store",
provider_metadata: {
password: passwordHash,
},
},
])
const res = await service.authenticate("emailpass", {
body: {
email: "test@test.com",
password: password,
},
authScope: "store",
} as any)
expect(res).toEqual({
success: true,
authUser: expect.objectContaining({
entity_id: email,
provider_metadata: {},
}),
})
})
it("fails when no password is given", async () => {
await seedDefaultData(MikroOrmWrapper.forkManager())
const res = await service.authenticate("emailpass", {
body: { email: "test@test.com" },
authScope: "store",
} as any)
expect(res).toEqual({
success: false,
error: "Password should be a string",
})
})
it("fails when no email is given", async () => {
await seedDefaultData(MikroOrmWrapper.forkManager())
const res = await service.authenticate("emailpass", {
body: { password: "supersecret" },
authScope: "store",
} as any)
expect(res).toEqual({
success: false,
error: "Email should be a string",
})
})
it("fails with an invalid password", async () => {
const password = "supersecret"
const email = "test@test.com"
const passwordHash = (
await Scrypt.kdf(password, { logN: 15, r: 8, p: 1 })
).toString("base64")
await seedDefaultData(MikroOrmWrapper.forkManager())
await createAuthUsers(MikroOrmWrapper.forkManager(), [
// Add authenticated user
{
provider: "emailpass",
scope: "store",
entity_id: email,
provider_metadata: {
password_hash: passwordHash,
},
},
])
const res = await service.authenticate("emailpass", {
body: {
email: "test@test.com",
password: "password",
},
authScope: "store",
} as any)
expect(res).toEqual({
success: false,
error: "Invalid email or password",
})
})
})
})
},
})

View File

@@ -0,0 +1,21 @@
module.exports = {
moduleNameMapper: {
"^@models": "<rootDir>/src/models",
"^@services": "<rootDir>/src/services",
"^@repositories": "<rootDir>/src/repositories",
"^@types": "<rootDir>/src/types",
"^@providers": "<rootDir>/src/providers",
},
transform: {
"^.+\\.[jt]s?$": [
"ts-jest",
{
tsconfig: "tsconfig.spec.json",
isolatedModules: true,
},
],
},
testEnvironment: `node`,
moduleFileExtensions: [`js`, `ts`],
modulePathIgnorePatterns: ["dist/"],
}

View File

@@ -0,0 +1,8 @@
import * as entities from "./src/models"
module.exports = {
entities: Object.values(entities),
schema: "public",
clientUrl: "postgres://postgres@localhost/medusa-auth",
type: "postgresql",
}

View File

@@ -0,0 +1,64 @@
{
"name": "@medusajs/auth",
"version": "0.0.3",
"description": "Medusa Auth module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"engines": {
"node": ">=16"
},
"bin": {
"medusa-auth-seed": "dist/scripts/bin/run-seed.js"
},
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
"directory": "packages/auth"
},
"publishConfig": {
"access": "public"
},
"author": "Medusa",
"license": "MIT",
"scripts": {
"watch": "tsc --build --watch",
"watch:test": "tsc --build tsconfig.spec.json --watch",
"prepublishOnly": "cross-env NODE_ENV=production tsc --build && tsc-alias -p tsconfig.json",
"build": "rimraf dist && tsc --build && tsc-alias -p tsconfig.json",
"test": "jest --runInBand --bail --passWithNoTests --forceExit -- src",
"test:integration": "jest --forceExit -- integration-tests/**/__tests__/**/*.ts",
"migration:generate": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:generate",
"migration:initial": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create --initial",
"migration:create": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create",
"migration:up": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:up",
"orm:cache:clear": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm cache:clear"
},
"devDependencies": {
"@mikro-orm/cli": "5.9.7",
"cross-env": "^5.2.1",
"jest": "^29.6.3",
"medusa-test-utils": "^1.1.42",
"rimraf": "^3.0.2",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"tsc-alias": "^1.8.6",
"typescript": "^5.1.6"
},
"dependencies": {
"@medusajs/modules-sdk": "^1.12.9",
"@medusajs/types": "^1.11.14",
"@medusajs/utils": "^1.11.7",
"@mikro-orm/core": "5.9.7",
"@mikro-orm/migrations": "5.9.7",
"@mikro-orm/postgresql": "5.9.7",
"awilix": "^8.0.0",
"dotenv": "^16.4.5",
"jsonwebtoken": "^9.0.2",
"knex": "2.4.2",
"scrypt-kdf": "^2.0.1",
"simple-oauth2": "^5.0.0"
}
}

View File

@@ -0,0 +1,11 @@
import {
moduleDefinition,
revertMigration,
runMigrations,
} from "./module-definition"
export default moduleDefinition
export { revertMigration, runMigrations }
export * from "./initialize"
export * from "./loaders"

View File

@@ -0,0 +1,31 @@
import {
ExternalModuleDeclaration,
InternalModuleDeclaration,
MedusaModule,
MODULE_PACKAGE_NAMES,
Modules,
} from "@medusajs/modules-sdk"
import { IAuthModuleService, ModulesSdkTypes } from "@medusajs/types"
import { InitializeModuleInjectableDependencies } from "@types"
import { moduleDefinition } from "../module-definition"
export const initialize = async (
options?:
| ModulesSdkTypes.ModuleBootstrapDeclaration
| ModulesSdkTypes.ModuleServiceInitializeOptions
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions,
injectedDependencies?: InitializeModuleInjectableDependencies
): Promise<IAuthModuleService> => {
const loaded = await MedusaModule.bootstrap<IAuthModuleService>({
moduleKey: Modules.AUTH,
defaultPath: MODULE_PACKAGE_NAMES[Modules.AUTH],
declaration: options as
| InternalModuleDeclaration
| ExternalModuleDeclaration, // TODO: Add provider configuration
injectedDependencies,
moduleExports: moduleDefinition,
})
return loaded[Modules.AUTH]
}

View File

@@ -0,0 +1,31 @@
import { AuthUser } from "@models"
import { MapToConfig } from "@medusajs/utils"
import { ModuleJoinerConfig } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
export const LinkableKeys = {
auth_user_id: AuthUser.name,
}
const entityLinkableKeysMap: MapToConfig = {}
Object.entries(LinkableKeys).forEach(([key, value]) => {
entityLinkableKeysMap[value] ??= []
entityLinkableKeysMap[value].push({
mapTo: key,
valueFrom: key.split("_").pop()!,
})
})
export const entityNameToLinkableKeysMap: MapToConfig = entityLinkableKeysMap
export const joinerConfig: ModuleJoinerConfig = {
serviceName: Modules.AUTH,
primaryKeys: ["id"],
linkableKeys: LinkableKeys,
alias: {
name: ["auth_user", "auth_users"],
args: {
entity: AuthUser.name,
},
},
}

View File

@@ -0,0 +1,38 @@
import * as AuthModels from "../models"
import {
InternalModuleDeclaration,
LoaderOptions,
Modules,
} from "@medusajs/modules-sdk"
import { EntitySchema } from "@mikro-orm/core"
import { ModulesSdkTypes } from "@medusajs/types"
import { ModulesSdkUtils } from "@medusajs/utils"
export default async (
{
options,
container,
logger,
}: LoaderOptions<
| ModulesSdkTypes.ModuleServiceInitializeOptions
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
>,
moduleDeclaration?: InternalModuleDeclaration
): Promise<void> => {
const entities = Object.values(
AuthModels
) as unknown as EntitySchema[]
const pathToMigrations = __dirname + "/../migrations"
await ModulesSdkUtils.mikroOrmConnectionLoader({
moduleName: Modules.AUTH,
entities,
container,
options,
moduleDeclaration,
logger,
pathToMigrations,
})
}

View File

@@ -0,0 +1,10 @@
import { ModulesSdkUtils } from "@medusajs/utils"
import * as ModuleModels from "@models"
import * as ModuleRepositories from "@repositories"
import * as ModuleServices from "@services"
export default ModulesSdkUtils.moduleContainerLoaderFactory({
moduleModels: ModuleModels,
moduleRepositories: ModuleRepositories,
moduleServices: ModuleServices,
})

View File

@@ -0,0 +1,3 @@
export * from "./connection"
export * from "./container"
export * from "./providers"

View File

@@ -0,0 +1,72 @@
import * as defaultProviders from "@providers"
import {
AuthModuleProviderConfig,
AuthProviderScope,
LoaderOptions,
ModulesSdkTypes,
} from "@medusajs/types"
import {
AwilixContainer,
ClassOrFunctionReturning,
Constructor,
Resolver,
asClass,
} from "awilix"
type AuthModuleProviders = {
providers: AuthModuleProviderConfig[]
}
export default async ({
container,
options,
}: LoaderOptions<
(
| ModulesSdkTypes.ModuleServiceInitializeOptions
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
) &
AuthModuleProviders
>): Promise<void> => {
const providerMap = new Map(
options?.providers?.map((provider) => [provider.name, provider.scopes]) ??
[]
)
// if(options?.providers?.length) {
// TODO: implement plugin provider registration
// }
const providersToLoad = Object.values(defaultProviders)
for (const provider of providersToLoad) {
container.register({
[`auth_provider_${provider.PROVIDER}`]: asClass(
provider as Constructor<any>
)
.singleton()
.inject(() => ({ scopes: providerMap.get(provider.PROVIDER) ?? {} })),
})
}
container.register({
[`auth_providers`]: asArray(providersToLoad, providerMap),
})
}
function asArray(
resolvers: (ClassOrFunctionReturning<unknown> | Resolver<unknown>)[],
providerScopeMap: Map<string, Record<string, AuthProviderScope>>
): { resolve: (container: AwilixContainer) => unknown[] } {
return {
resolve: (container: AwilixContainer) =>
resolvers.map((resolver) =>
asClass(resolver as Constructor<any>)
.inject(() => ({
// @ts-ignore
scopes: providerScopeMap.get(resolver.PROVIDER) ?? {},
}))
.resolve(container)
),
}
}

View File

@@ -0,0 +1,101 @@
{
"namespaces": [
"public"
],
"name": "public",
"tables": [
{
"columns": {
"id": {
"name": "id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"entity_id": {
"name": "entity_id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"provider": {
"name": "provider",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"scope": {
"name": "scope",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"user_metadata": {
"name": "user_metadata",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"app_metadata": {
"name": "app_metadata",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "json"
},
"provider_metadata": {
"name": "provider_metadata",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
}
},
"name": "auth_user",
"schema": "public",
"indexes": [
{
"keyName": "IDX_auth_user_provider_scope_entity_id",
"columnNames": [
"provider",
"scope",
"entity_id"
],
"composite": true,
"primary": false,
"unique": true
},
{
"keyName": "auth_user_pkey",
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
}
]
}

View File

@@ -0,0 +1,16 @@
import { Migration } from "@mikro-orm/migrations"
export class Migration20240205025924 extends Migration {
async up(): Promise<void> {
this.addSql(
'create table if not exists "auth_user" ("id" text not null, "entity_id" text not null, "provider" text not null, "scope" text not null, "user_metadata" jsonb null, "app_metadata" jsonb not null, "provider_metadata" jsonb null, constraint "auth_user_pkey" primary key ("id"));'
)
this.addSql(
'alter table "auth_user" add constraint "IDX_auth_user_provider_scope_entity_id" unique ("provider", "scope", "entity_id");'
)
}
async down(): Promise<void> {
this.addSql('drop table if exists "auth_user" cascade;')
}
}

View File

@@ -0,0 +1,53 @@
import {
BeforeCreate,
Entity,
OnInit,
OptionalProps,
PrimaryKey,
Property,
Unique,
} from "@mikro-orm/core"
import { generateEntityId } from "@medusajs/utils"
type OptionalFields = "provider_metadata" | "app_metadata" | "user_metadata"
@Entity()
@Unique({
properties: ["provider", "scope", "entity_id"],
name: "IDX_auth_user_provider_scope_entity_id",
})
export default class AuthUser {
[OptionalProps]: OptionalFields
@PrimaryKey({ columnType: "text" })
id!: string
@Property({ columnType: "text" })
entity_id: string
@Property({ columnType: "text" })
provider: string
@Property({ columnType: "text" })
scope: string
@Property({ columnType: "jsonb", nullable: true })
user_metadata: Record<string, unknown> | null
@Property({ columnType: "jsonb" })
app_metadata: Record<string, unknown> = {}
@Property({ columnType: "jsonb", nullable: true })
provider_metadata: Record<string, unknown> | null = null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "authusr")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "authusr")
}
}

View File

@@ -0,0 +1 @@
export { default as AuthUser } from "./auth-user"

View File

@@ -0,0 +1,32 @@
import * as Models from "@models"
import { AuthModuleService } from "@services"
import { ModuleExports } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
import { ModulesSdkUtils } from "@medusajs/utils"
import loadConnection from "./loaders/connection"
import loadContainer from "./loaders/container"
import loadProviders from "./loaders/providers"
const migrationScriptOptions = {
moduleName: Modules.AUTH,
models: Models,
pathToMigrations: __dirname + "/migrations",
}
export const runMigrations = ModulesSdkUtils.buildMigrationScript(
migrationScriptOptions
)
export const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
migrationScriptOptions
)
const service = AuthModuleService
const loaders = [loadContainer, loadConnection, loadProviders] as any
export const moduleDefinition: ModuleExports = {
service,
loaders,
runMigrations,
revertMigration,
}

View File

@@ -0,0 +1,106 @@
import { AuthenticationInput, AuthenticationResponse } from "@medusajs/types"
import {
AbstractAuthModuleProvider,
MedusaError,
isString,
} from "@medusajs/utils"
import { AuthUserService } from "@services"
import Scrypt from "scrypt-kdf"
class EmailPasswordProvider extends AbstractAuthModuleProvider {
public static PROVIDER = "emailpass"
public static DISPLAY_NAME = "Email/Password Authentication"
protected readonly authUserSerivce_: AuthUserService
constructor({ authUserService }: { authUserService: AuthUserService }) {
super(arguments[0], {
provider: EmailPasswordProvider.PROVIDER,
displayName: EmailPasswordProvider.DISPLAY_NAME,
})
this.authUserSerivce_ = authUserService
}
private getHashConfig() {
const scopeConfig = this.scopeConfig_.hashConfig as
| Scrypt.ScryptParams
| undefined
const defaultHashConfig = { logN: 15, r: 8, p: 1 }
// Return custom defined hash config or default hash parameters
return scopeConfig ?? defaultHashConfig
}
async authenticate(
userData: AuthenticationInput
): Promise<AuthenticationResponse> {
const { email, password } = userData.body
if (!password || !isString(password)) {
return {
success: false,
error: "Password should be a string",
}
}
if (!email || !isString(email)) {
return {
success: false,
error: "Email should be a string",
}
}
let authUser
try {
authUser = await this.authUserSerivce_.retrieveByProviderAndEntityId(
email,
EmailPasswordProvider.PROVIDER
)
} catch (error) {
if (error.type === MedusaError.Types.NOT_FOUND) {
const password_hash = await Scrypt.kdf(password, this.getHashConfig())
const [createdAuthUser] = await this.authUserSerivce_.create([
{
entity_id: email,
provider: EmailPasswordProvider.PROVIDER,
scope: this.scope_,
provider_metadata: {
password: password_hash.toString("base64"),
},
},
])
return {
success: true,
authUser: JSON.parse(JSON.stringify(createdAuthUser)),
}
}
return { success: false, error: error.message }
}
const password_hash = authUser.provider_metadata?.password
if (isString(password_hash)) {
const buf = Buffer.from(password_hash as string, "base64")
const success = await Scrypt.verify(buf, password)
if (success) {
delete authUser.provider_metadata!.password
return { success, authUser: JSON.parse(JSON.stringify(authUser)) }
}
}
return {
success: false,
error: "Invalid email or password",
}
}
}
export default EmailPasswordProvider

View File

@@ -0,0 +1,223 @@
import { AuthenticationInput, AuthenticationResponse } from "@medusajs/types"
import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils"
import { AuthUserService } from "@services"
import jwt, { JwtPayload } from "jsonwebtoken"
import { AuthorizationCode } from "simple-oauth2"
import url from "url"
type InjectedDependencies = {
authUserService: AuthUserService
}
type ProviderConfig = {
clientID: string
clientSecret: string
callbackURL: string
successRedirectUrl?: string
}
class GoogleProvider extends AbstractAuthModuleProvider {
public static PROVIDER = "google"
public static DISPLAY_NAME = "Google Authentication"
protected readonly authUserService_: AuthUserService
constructor({ authUserService }: InjectedDependencies) {
super(arguments[0], {
provider: GoogleProvider.PROVIDER,
displayName: GoogleProvider.DISPLAY_NAME,
})
this.authUserService_ = authUserService
}
async authenticate(
req: AuthenticationInput
): Promise<AuthenticationResponse> {
if (req.query?.error) {
return {
success: false,
error: `${req.query.error_description}, read more at: ${req.query.error_uri}`,
}
}
let config: ProviderConfig
try {
config = await this.getProviderConfig(req)
} catch (error) {
return { success: false, error: error.message }
}
return this.getRedirect(config)
}
async validateCallback(
req: AuthenticationInput
): Promise<AuthenticationResponse> {
if (req.query && req.query.error) {
return {
success: false,
error: `${req.query.error_description}, read more at: ${req.query.error_uri}`,
}
}
let config: ProviderConfig
try {
config = await this.getProviderConfig(req)
} catch (error) {
return { success: false, error: error.message }
}
const code = req.query?.code ?? req.body?.code
return await this.validateCallbackToken(code, config)
}
// abstractable
async verify_(refreshToken: string) {
const jwtData = jwt.decode(refreshToken, {
complete: true,
}) as JwtPayload
const entity_id = jwtData.payload.email
let authUser
try {
authUser = await this.authUserService_.retrieveByProviderAndEntityId(
entity_id,
GoogleProvider.PROVIDER
)
} catch (error) {
if (error.type === MedusaError.Types.NOT_FOUND) {
const [createdAuthUser] = await this.authUserService_.create([
{
entity_id,
provider: GoogleProvider.PROVIDER,
user_metadata: jwtData!.payload,
scope: this.scope_,
},
])
authUser = createdAuthUser
} else {
return { success: false, error: error.message }
}
}
return {
success: true,
authUser,
}
}
// abstractable
private async validateCallbackToken(
code: string,
{ clientID, callbackURL, clientSecret }: ProviderConfig
) {
const client = this.getAuthorizationCodeHandler({ clientID, clientSecret })
const tokenParams = {
code,
redirect_uri: callbackURL,
}
try {
const accessToken = await client.getToken(tokenParams)
const { authUser, success } = await this.verify_(
accessToken.token.id_token
)
const { successRedirectUrl } = this.getConfigFromScope()
return {
success,
authUser,
successRedirectUrl,
}
} catch (error) {
return { success: false, error: error.message }
}
}
private getConfigFromScope(): ProviderConfig {
const config: Partial<ProviderConfig> = { ...this.scopeConfig_ }
if (!config.clientID) {
throw new Error("Google clientID is required")
}
if (!config.clientSecret) {
throw new Error("Google clientSecret is required")
}
if (!config.callbackURL) {
throw new Error("Google callbackUrl is required")
}
return config as ProviderConfig
}
private originalURL(req: AuthenticationInput) {
const host = req.headers.host
const protocol = req.protocol
const path = req.url || ""
return protocol + "://" + host + path
}
private async getProviderConfig(
req: AuthenticationInput
): Promise<ProviderConfig> {
const config = this.getConfigFromScope()
const callbackURL = config.callbackURL
const parsedCallbackUrl = !url.parse(callbackURL).protocol
? url.resolve(this.originalURL(req), callbackURL)
: callbackURL
return { ...config, callbackURL: parsedCallbackUrl }
}
// Abstractable
private getRedirect({ clientID, callbackURL, clientSecret }: ProviderConfig) {
const client = this.getAuthorizationCodeHandler({ clientID, clientSecret })
const location = client.authorizeURL({
redirect_uri: callbackURL,
scope: "email profile",
})
return { success: true, location }
}
private getAuthorizationCodeHandler({
clientID,
clientSecret,
}: {
clientID: string
clientSecret: string
}) {
const config = {
client: {
id: clientID,
secret: clientSecret,
},
auth: {
// TODO: abstract to not be google specific
authorizeHost: "https://accounts.google.com",
authorizePath: "/o/oauth2/v2/auth",
tokenHost: "https://www.googleapis.com",
tokenPath: "/oauth2/v4/token",
},
}
return new AuthorizationCode(config)
}
}
export default GoogleProvider

View File

@@ -0,0 +1,2 @@
export { default as EmailPasswordProvider } from "./email-password"
export { default as GoogleProvider } from "./google"

View File

@@ -0,0 +1 @@
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"

View File

@@ -0,0 +1,19 @@
#!/usr/bin/env node
import { EOL } from "os"
import { run } from "../seed"
const args = process.argv
const path = args.pop() as string
export default (async () => {
const { config } = await import("dotenv")
config()
if (!path) {
throw new Error(
`filePath is required.${EOL}Example: medusa-auth-seed <filePath>`
)
}
await run({ path })
})()

View File

@@ -0,0 +1,65 @@
import * as AuthModels from "@models"
import { DALUtils, ModulesSdkUtils } from "@medusajs/utils"
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
import { EOL } from "os"
import { EntitySchema } from "@mikro-orm/core"
import { Modules } from "@medusajs/modules-sdk"
import { resolve } from "path"
export async function run({
options,
logger,
path,
}: Partial<
Pick<
LoaderOptions<ModulesSdkTypes.ModuleServiceInitializeOptions>,
"options" | "logger"
>
> & {
path: string
}) {
logger ??= console as unknown as Logger
logger.info(`Loading seed data from ${path}...`)
const { authenticationData } = await import(
resolve(process.cwd(), path)
).catch((e) => {
logger?.error(
`Failed to load seed data from ${path}. Please, provide a relative path and check that you export the following: authenticationData.${EOL}${e}`
)
throw e
})
const dbData = ModulesSdkUtils.loadDatabaseConfig(
Modules.AUTH,
options
)!
const entities = Object.values(
AuthModels
) as unknown as EntitySchema[]
const pathToMigrations = __dirname + "/../migrations"
const orm = await DALUtils.mikroOrmCreateConnection(
dbData,
entities,
pathToMigrations
)
const manager = orm.em.fork()
try {
logger.info("Seeding authentication data..")
// TODO: implement authentication seed data
// await createAuthUsers(manager, authUsersData)
} catch (e) {
logger.error(
`Failed to insert the seed data in the PostgreSQL database ${dbData.clientUrl}.${EOL}${e}`
)
}
await orm.close(true)
}

View File

@@ -0,0 +1,156 @@
import {
AuthenticationInput,
AuthenticationResponse,
AuthTypes,
AuthUserDTO,
Context,
CreateAuthUserDTO,
DAL,
InternalModuleDeclaration,
ModuleJoinerConfig,
ModulesSdkTypes,
UpdateAuthUserDTO,
} from "@medusajs/types"
import { AuthUser } from "@models"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import {
AbstractAuthModuleProvider,
InjectManager,
MedusaContext,
MedusaError,
ModulesSdkUtils,
} from "@medusajs/utils"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
authUserService: ModulesSdkTypes.InternalModuleService<any>
}
const generateMethodForModels = [AuthUser]
export default class AuthModuleService<TAuthUser extends AuthUser = AuthUser>
extends ModulesSdkUtils.abstractModuleServiceFactory<
InjectedDependencies,
AuthTypes.AuthUserDTO,
{
AuthUser: { dto: AuthUserDTO }
}
>(AuthUser, generateMethodForModels, entityNameToLinkableKeysMap)
implements AuthTypes.IAuthModuleService
{
protected baseRepository_: DAL.RepositoryService
protected authUserService_: ModulesSdkTypes.InternalModuleService<TAuthUser>
constructor(
{ authUserService, baseRepository }: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
// @ts-ignore
super(...arguments)
this.baseRepository_ = baseRepository
this.authUserService_ = authUserService
}
__joinerConfig(): ModuleJoinerConfig {
return joinerConfig
}
create(
data: CreateAuthUserDTO[],
sharedContext?: Context
): Promise<AuthUserDTO[]>
create(data: CreateAuthUserDTO, sharedContext?: Context): Promise<AuthUserDTO>
@InjectManager("baseRepository_")
async create(
data: CreateAuthUserDTO[] | CreateAuthUserDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<AuthTypes.AuthUserDTO | AuthTypes.AuthUserDTO[]> {
const authUsers = await this.authUserService_.create(data, sharedContext)
return await this.baseRepository_.serialize<AuthTypes.AuthUserDTO[]>(
authUsers,
{
populate: true,
}
)
}
update(
data: UpdateAuthUserDTO[],
sharedContext?: Context
): Promise<AuthUserDTO[]>
update(data: UpdateAuthUserDTO, sharedContext?: Context): Promise<AuthUserDTO>
// TODO: should be pluralized, see convention about the methods naming or the abstract module service interface definition @engineering
@InjectManager("baseRepository_")
async update(
data: UpdateAuthUserDTO | UpdateAuthUserDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<AuthTypes.AuthUserDTO | AuthTypes.AuthUserDTO[]> {
const updatedUsers = await this.authUserService_.update(data, sharedContext)
const serializedUsers = await this.baseRepository_.serialize<
AuthTypes.AuthUserDTO[]
>(updatedUsers, {
populate: true,
})
return Array.isArray(data) ? serializedUsers : serializedUsers[0]
}
protected getRegisteredAuthenticationProvider(
provider: string,
{ authScope }: AuthenticationInput
): AbstractAuthModuleProvider {
let containerProvider: AbstractAuthModuleProvider
try {
containerProvider = this.__container__[`auth_provider_${provider}`]
} catch (error) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`AuthenticationProvider: ${provider} wasn't registered in the module. Have you configured your options correctly?`
)
}
return containerProvider.withScope(authScope)
}
async authenticate(
provider: string,
authenticationData: AuthenticationInput
): Promise<AuthenticationResponse> {
try {
const registeredProvider = this.getRegisteredAuthenticationProvider(
provider,
authenticationData
)
return await registeredProvider.authenticate(authenticationData)
} catch (error) {
return { success: false, error: error.message }
}
}
async validateCallback(
provider: string,
authenticationData: AuthenticationInput
): Promise<AuthenticationResponse> {
try {
const registeredProvider = this.getRegisteredAuthenticationProvider(
provider,
authenticationData
)
return await registeredProvider.validateCallback(authenticationData)
} catch (error) {
return { success: false, error: error.message }
}
}
}

View File

@@ -0,0 +1,61 @@
import {
AuthTypes,
Context,
DAL,
FindConfig,
RepositoryService,
} from "@medusajs/types"
import {
InjectManager,
MedusaContext,
MedusaError,
ModulesSdkUtils,
} from "@medusajs/utils"
import { AuthUser } from "@models"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
authUserRepository: DAL.RepositoryService
}
export default class AuthUserService<
TEntity extends AuthUser = AuthUser
> extends ModulesSdkUtils.internalModuleServiceFactory<InjectedDependencies>(
AuthUser
)<TEntity> {
protected readonly authUserRepository_: RepositoryService<TEntity>
protected baseRepository_: DAL.RepositoryService
constructor(container: InjectedDependencies) {
// @ts-ignore
super(...arguments)
this.authUserRepository_ = container.authUserRepository
this.baseRepository_ = container.baseRepository
}
@InjectManager("authUserRepository_")
async retrieveByProviderAndEntityId<TEntityMethod = AuthTypes.AuthUserDTO>(
entityId: string,
provider: string,
config: FindConfig<TEntityMethod> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<AuthTypes.AuthUserDTO> {
const queryConfig = ModulesSdkUtils.buildQuery<TEntity>(
{ entity_id: entityId, provider },
{ ...config, take: 1 }
)
const [result] = await this.authUserRepository_.find(
queryConfig,
sharedContext
)
if (!result) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`AuthUser with entity_id: "${entityId}" and provider: "${provider}" not found`
)
}
return await this.baseRepository_.serialize<AuthTypes.AuthUserDTO>(result)
}
}

View File

@@ -0,0 +1,2 @@
export { default as AuthModuleService } from "./auth-module"
export { default as AuthUserService } from "./auth-user"

View File

@@ -0,0 +1,8 @@
import { Logger } from "@medusajs/types"
export type InitializeModuleInjectableDependencies = {
logger?: Logger
}
export * as RepositoryTypes from "./repositories"
export * as ServiceTypes from "./services"

View File

@@ -0,0 +1,19 @@
import { AuthUser } from "@models"
export type CreateAuthUserDTO = {
provider_id: string
entity_id: string
provider_metadata?: Record<string, unknown>
user_metadata?: Record<string, unknown>
app_metadata?: Record<string, unknown>
}
export type UpdateAuthUserDTO = {
update: {
id: string
provider_metadata?: Record<string, unknown>
user_metadata?: Record<string, unknown>
app_metadata?: Record<string, unknown>
}
user: AuthUser
}

View File

@@ -0,0 +1 @@
export * from "./auth-user"

View File

@@ -0,0 +1,28 @@
export type AuthUserDTO = {
id: string
provider_id: string
entity_id: string
scope: string
provider: string
provider_metadata?: Record<string, unknown>
user_metadata: Record<string, unknown>
app_metadata: Record<string, unknown>
}
export type CreateAuthUserDTO = {
entity_id: string
provider: string
scope: string
provider_metadata?: Record<string, unknown>
user_metadata?: Record<string, unknown>
app_metadata?: Record<string, unknown>
}
export type UpdateAuthUserDTO = {
id: string
provider_metadata?: Record<string, unknown>
user_metadata?: Record<string, unknown>
app_metadata?: Record<string, unknown>
}
export type FilterableAuthUserProps = {}

View File

@@ -0,0 +1 @@
export * from "./auth-user"

View File

@@ -0,0 +1,53 @@
{
"compilerOptions": {
"lib": [
"es2020"
],
"target": "es2020",
"outDir": "./dist",
"esModuleInterop": true,
"declaration": true,
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": false,
"noImplicitReturns": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"allowJs": true,
"skipLibCheck": true,
"downlevelIteration": true,
// to use ES5 specific tooling
"baseUrl": ".",
"resolveJsonModule": true,
"paths": {
"@models": [
"./src/models"
],
"@services": [
"./src/services"
],
"@repositories": [
"./src/repositories"
],
"@types": [
"./src/types"
],
"@providers": [
"./src/providers"
]
}
},
"include": [
"src"
],
"exclude": [
"dist",
"./src/**/__tests__",
"./src/**/__mocks__",
"./src/**/__fixtures__",
"node_modules"
]
}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"include": ["src", "integration-tests"],
"exclude": ["node_modules", "dist"],
"compilerOptions": {
"sourceMap": true
}
}