chore(): Reorganize modules (#7210)
**What** Move all modules to the modules directory
This commit is contained in:
committed by
GitHub
parent
7a351eef09
commit
4eae25e1ef
6
packages/modules/api-key/.gitignore
vendored
Normal file
6
packages/modules/api-key/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/dist
|
||||
node_modules
|
||||
.DS_store
|
||||
.env*
|
||||
.env
|
||||
*.sql
|
||||
23
packages/modules/api-key/CHANGELOG.md
Normal file
23
packages/modules/api-key/CHANGELOG.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# @medusajs/api-key
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#6851](https://github.com/medusajs/medusa/pull/6851) [`ea8d9d4d42`](https://github.com/medusajs/medusa/commit/ea8d9d4d42210a5598b308656922c0e93c90b7c8) Thanks [@olivermrbl](https://github.com/olivermrbl)! - feat: API key sales channel link
|
||||
|
||||
- Updated dependencies [[`0c0b425de7`](https://github.com/medusajs/medusa/commit/0c0b425de7b154b80b712ab17b16215cf62d1e83), [`8d356217bd`](https://github.com/medusajs/medusa/commit/8d356217bd31c97a196e861ee243822a4d924df7), [`1eeb1e9de3`](https://github.com/medusajs/medusa/commit/1eeb1e9de3e0b571735437b00968ee96e4aabad5), [`20e8df914e`](https://github.com/medusajs/medusa/commit/20e8df914ec5fdf8d562d4fa84f72c58c7056195), [`27f4f0d724`](https://github.com/medusajs/medusa/commit/27f4f0d7243367c2dfc6012bf1f6b7400a77ec7b), [`e0b02a1012`](https://github.com/medusajs/medusa/commit/e0b02a1012981c29830d7779f59ebe805bbfd137), [`e944a627f0`](https://github.com/medusajs/medusa/commit/e944a627f074fb39a56f4bc7b3d6d315736ebf7c), [`1a48fe0282`](https://github.com/medusajs/medusa/commit/1a48fe0282a8bc1f8548a4736255e457d173da09), [`86f499de2f`](https://github.com/medusajs/medusa/commit/86f499de2f31356ab36ad5e93f27345443b3e5f6), [`09a2220569`](https://github.com/medusajs/medusa/commit/09a22205693da62fbf8fd450535d5024cb9c01d1), [`78f603e4f1`](https://github.com/medusajs/medusa/commit/78f603e4f18c9d16f4b58a2189c959026453d8b2), [`cc557c8752`](https://github.com/medusajs/medusa/commit/cc557c8752fd0554f5a1b58522d9a88dc43a8509), [`dd35a4dbff`](https://github.com/medusajs/medusa/commit/dd35a4dbff10c86ea3c5f7f817c18b6e60d599e3), [`58c68f6715`](https://github.com/medusajs/medusa/commit/58c68f67156e993255fbc25d91db15ae23bc95c0), [`1bcb13f892`](https://github.com/medusajs/medusa/commit/1bcb13f892bc61db21b3fc6bdbce85f747aeec4c), [`82a176e30e`](https://github.com/medusajs/medusa/commit/82a176e30e47a7d11caaf31c3023bd8db588b465), [`11517f0faf`](https://github.com/medusajs/medusa/commit/11517f0fafdf00af256240448b58d149d8b6f600), [`62b9dcc6c1`](https://github.com/medusajs/medusa/commit/62b9dcc6c1ce46aadb7944215006c12da3c9f619), [`5d9aea053c`](https://github.com/medusajs/medusa/commit/5d9aea053ce6e04f242f86fb9053c13dec515d5b), [`e26cda4b6a`](https://github.com/medusajs/medusa/commit/e26cda4b6afb7fb25f0b0a7a7ce20b7f914d35db), [`bc06ad2db4`](https://github.com/medusajs/medusa/commit/bc06ad2db48c999023ab823fefc1375196976e9b), [`18f3aacee6`](https://github.com/medusajs/medusa/commit/18f3aacee6752854d377faa806f4cc67bc71456b), [`232322d035`](https://github.com/medusajs/medusa/commit/232322d03515f81e56867ff8c765b8409399ee68), [`38c971f111`](https://github.com/medusajs/medusa/commit/38c971f111af69f176e7e9892eb59f5bae831fa7), [`45c49e89f2`](https://github.com/medusajs/medusa/commit/45c49e89f28123ef622fc1c07253bae94fd74875), [`528ef4ca90`](https://github.com/medusajs/medusa/commit/528ef4ca90bb2cf6173dccc9fd6a9f9932ff9b76), [`65794f4bb5`](https://github.com/medusajs/medusa/commit/65794f4bb56e4fd3f0ccb7656a948f856f05324e), [`93ef94cad3`](https://github.com/medusajs/medusa/commit/93ef94cad3ddc5b6973b4e48e422b0aa0e6ddbbe), [`4cf71af07d`](https://github.com/medusajs/medusa/commit/4cf71af07d1807c83df3889c1774f82cbd1b9a6f), [`4b57c5d286`](https://github.com/medusajs/medusa/commit/4b57c5d286f9dc6e2098c67e9fecb0d93175b5a1), [`c78915c7c5`](https://github.com/medusajs/medusa/commit/c78915c7c5e91a99c1b1bae932656c8d86b17daf), [`18f3aacee6`](https://github.com/medusajs/medusa/commit/18f3aacee6752854d377faa806f4cc67bc71456b), [`667c8609cc`](https://github.com/medusajs/medusa/commit/667c8609ccf3850f5df8cf784723a95bd0d6d2a6), [`f175cac4af`](https://github.com/medusajs/medusa/commit/f175cac4af63b71066a8398ecf9beaa6f28b20cc), [`0a9b9b073d`](https://github.com/medusajs/medusa/commit/0a9b9b073dd2d3f4aa5e5cb1c16e2221a7200e0d), [`a6562d2a41`](https://github.com/medusajs/medusa/commit/a6562d2a41453cbe7aa43be352c4924e3e4c79d5), [`00e6b21bb5`](https://github.com/medusajs/medusa/commit/00e6b21bb50dbc886bc37ad052a1c40ce865294e), [`8fd1488938`](https://github.com/medusajs/medusa/commit/8fd148893850eb66c5eae00c4ca9391a80ea2eb9), [`1c6ba4468e`](https://github.com/medusajs/medusa/commit/1c6ba4468eab1440931c88929affd5b4c593f377)]:
|
||||
- @medusajs/types@1.11.16
|
||||
- @medusajs/modules-sdk@1.12.11
|
||||
- @medusajs/utils@1.11.9
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### 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
|
||||
1
packages/modules/api-key/README.md
Normal file
1
packages/modules/api-key/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# API Key Module
|
||||
@@ -0,0 +1,14 @@
|
||||
import { CreateApiKeyDTO } from "@types"
|
||||
import { ApiKeyType } from "@medusajs/utils"
|
||||
|
||||
export const createSecretKeyFixture: CreateApiKeyDTO = {
|
||||
title: "Secret key",
|
||||
type: ApiKeyType.SECRET,
|
||||
created_by: "test",
|
||||
}
|
||||
|
||||
export const createPublishableKeyFixture: CreateApiKeyDTO = {
|
||||
title: "Test API Key",
|
||||
type: ApiKeyType.PUBLISHABLE,
|
||||
created_by: "test",
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
import crypto from "crypto"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { IApiKeyModuleService } from "@medusajs/types"
|
||||
import { ApiKeyType } from "@medusajs/utils"
|
||||
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
||||
import {
|
||||
createSecretKeyFixture,
|
||||
createPublishableKeyFixture,
|
||||
} from "../__fixtures__"
|
||||
|
||||
jest.setTimeout(100000)
|
||||
|
||||
const mockPublishableKeyBytes = () => {
|
||||
jest.spyOn(crypto, "randomBytes").mockImplementationOnce(() => {
|
||||
return Buffer.from(
|
||||
"44de31ebcf085fa423fc584aa854067025e937a79edb565f472404345f0f23be",
|
||||
"hex"
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const mockSecretKeyBytes = () => {
|
||||
jest
|
||||
.spyOn(crypto, "randomBytes")
|
||||
.mockImplementationOnce(() => {
|
||||
return Buffer.from(
|
||||
"44de31ebcf085fa423fc584aa854067025e937a79edb565f472404345f0f23be",
|
||||
"hex"
|
||||
)
|
||||
})
|
||||
.mockImplementationOnce(() => {
|
||||
return Buffer.from("44de31ebcf085fa423fc584aa8540670", "hex")
|
||||
})
|
||||
}
|
||||
|
||||
moduleIntegrationTestRunner({
|
||||
moduleName: Modules.API_KEY,
|
||||
testSuite: ({
|
||||
MikroOrmWrapper,
|
||||
service,
|
||||
}: SuiteOptions<IApiKeyModuleService>) => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe("API Key Module Service", () => {
|
||||
describe("creating a publishable API key", () => {
|
||||
it("should create it successfully", async function () {
|
||||
mockPublishableKeyBytes()
|
||||
const apiKey = await service.create(createPublishableKeyFixture)
|
||||
|
||||
expect(apiKey).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "Test API Key",
|
||||
type: ApiKeyType.PUBLISHABLE,
|
||||
salt: undefined,
|
||||
created_by: "test",
|
||||
last_used_at: null,
|
||||
revoked_by: null,
|
||||
revoked_at: null,
|
||||
redacted: "pk_44d***3be",
|
||||
token:
|
||||
"pk_44de31ebcf085fa423fc584aa854067025e937a79edb565f472404345f0f23be",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("creating a secret API key", () => {
|
||||
it("should get created successfully", async function () {
|
||||
mockSecretKeyBytes()
|
||||
const apiKey = await service.create(createSecretKeyFixture)
|
||||
|
||||
expect(apiKey).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "Secret key",
|
||||
type: ApiKeyType.SECRET,
|
||||
salt: undefined,
|
||||
created_by: "test",
|
||||
last_used_at: null,
|
||||
revoked_by: null,
|
||||
revoked_at: null,
|
||||
redacted: "sk_44d***3be",
|
||||
token:
|
||||
"sk_44de31ebcf085fa423fc584aa854067025e937a79edb565f472404345f0f23be",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should only allow creating one active token", async function () {
|
||||
expect(
|
||||
service.create([createSecretKeyFixture, createSecretKeyFixture])
|
||||
).rejects.toThrow(
|
||||
"You can only create one secret key at a time. You tried to create 2 secret keys."
|
||||
)
|
||||
|
||||
await service.create(createSecretKeyFixture)
|
||||
const err = await service
|
||||
.create(createSecretKeyFixture)
|
||||
.catch((e) => e)
|
||||
expect(err.message).toEqual(
|
||||
"You can only have one active secret key a time. Revoke or delete your existing key before creating a new one."
|
||||
)
|
||||
})
|
||||
|
||||
it("should allow for at most two tokens, where one is revoked", async function () {
|
||||
const firstApiKey = await service.create(createSecretKeyFixture)
|
||||
await service.revoke(
|
||||
{ id: firstApiKey.id },
|
||||
{
|
||||
revoked_by: "test",
|
||||
}
|
||||
)
|
||||
|
||||
await service.create(createSecretKeyFixture)
|
||||
const err = await service
|
||||
.create(createSecretKeyFixture)
|
||||
.catch((e) => e)
|
||||
expect(err.message).toEqual(
|
||||
"You can only have one active secret key a time. Revoke or delete your existing key before creating a new one."
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("revoking API keys", () => {
|
||||
it("should have the revoked at and revoked by set when a key is revoked", async function () {
|
||||
const firstApiKey = await service.create(createSecretKeyFixture)
|
||||
const revokedKey = await service.revoke(firstApiKey.id, {
|
||||
revoked_by: "test",
|
||||
})
|
||||
|
||||
expect(revokedKey).toEqual(
|
||||
expect.objectContaining({
|
||||
revoked_by: "test",
|
||||
revoked_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should be able to revoke a key in the future", async function () {
|
||||
const now = Date.parse("2021-01-01T00:00:00Z")
|
||||
const hourInSec = 3600
|
||||
jest.useFakeTimers().setSystemTime(now)
|
||||
|
||||
const createdKey = await service.create(createSecretKeyFixture)
|
||||
const revokedKey = await service.revoke(createdKey.id, {
|
||||
revoked_by: "test",
|
||||
revoke_in: hourInSec,
|
||||
})
|
||||
|
||||
expect(revokedKey).toEqual(
|
||||
expect.objectContaining({
|
||||
revoked_by: "test",
|
||||
revoked_at: new Date(now + hourInSec * 1000),
|
||||
})
|
||||
)
|
||||
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it("should do nothing if the revokal list is empty", async function () {
|
||||
const firstApiKey = await service.create(createSecretKeyFixture)
|
||||
let revokedKeys = await service.revoke([])
|
||||
expect(revokedKeys).toHaveLength(0)
|
||||
|
||||
const apiKey = await service.retrieve(firstApiKey.id)
|
||||
expect(apiKey.revoked_at).toBeFalsy()
|
||||
expect(apiKey.revoked_by).toBeFalsy()
|
||||
})
|
||||
|
||||
it("should not allow revoking an already revoked API key", async function () {
|
||||
const firstApiKey = await service.create(createSecretKeyFixture)
|
||||
await service.revoke(firstApiKey.id, {
|
||||
revoked_by: "test",
|
||||
})
|
||||
|
||||
const err = await service
|
||||
.revoke(firstApiKey.id, {
|
||||
revoked_by: "test2",
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err.message).toEqual(
|
||||
`There are 1 secret keys that are already revoked.`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("updating an API key", () => {
|
||||
it("should update the name successfully", async function () {
|
||||
const createdApiKey = await service.create(createSecretKeyFixture)
|
||||
|
||||
const updatedApiKey = await service.update(createdApiKey.id, {
|
||||
title: "New Name",
|
||||
})
|
||||
expect(updatedApiKey.title).toEqual("New Name")
|
||||
})
|
||||
|
||||
it("should not reflect any updates on other fields", async function () {
|
||||
const createdApiKey = await service.create(createSecretKeyFixture)
|
||||
|
||||
const updatedApiKey = await service.update(createdApiKey.id, {
|
||||
title: createdApiKey.title,
|
||||
revoked_by: "test",
|
||||
revoked_at: new Date(),
|
||||
last_used_at: new Date(),
|
||||
})
|
||||
|
||||
// These should not be returned on an update
|
||||
createdApiKey.token = ""
|
||||
expect(createdApiKey).toEqual(updatedApiKey)
|
||||
})
|
||||
})
|
||||
|
||||
describe("deleting API keys", () => {
|
||||
it("should successfully delete existing api keys", async function () {
|
||||
const createdApiKeys = await service.create([
|
||||
createPublishableKeyFixture,
|
||||
createSecretKeyFixture,
|
||||
])
|
||||
await service.delete([createdApiKeys[0].id, createdApiKeys[1].id])
|
||||
|
||||
const apiKeysInDatabase = await service.list()
|
||||
expect(apiKeysInDatabase).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("authenticating with API keys", () => {
|
||||
it("should authenticate a secret key successfully", async function () {
|
||||
const createdApiKey = await service.create(createSecretKeyFixture)
|
||||
const authenticated = await service.authenticate(createdApiKey.token)
|
||||
|
||||
expect(authenticated).toBeTruthy()
|
||||
expect(authenticated.title).toEqual(createSecretKeyFixture.title)
|
||||
})
|
||||
it("should authenticate with a token to be revoked in the future", async function () {
|
||||
const createdApiKey = await service.create(createSecretKeyFixture)
|
||||
|
||||
// We simulate setting the revoked_at in the future here
|
||||
jest.useFakeTimers().setSystemTime(new Date().setFullYear(3000))
|
||||
await service.revoke(createdApiKey.id, {
|
||||
revoked_by: "test",
|
||||
})
|
||||
jest.useRealTimers()
|
||||
|
||||
const authenticated = await service.authenticate(createdApiKey.token)
|
||||
expect(authenticated).toBeTruthy()
|
||||
expect(authenticated.title).toEqual(createdApiKey.title)
|
||||
})
|
||||
|
||||
it("should not authenticate a publishable key", async function () {
|
||||
const createdApiKey = await service.create(
|
||||
createPublishableKeyFixture
|
||||
)
|
||||
const authenticated = await service.authenticate(createdApiKey.token)
|
||||
|
||||
expect(authenticated).toBeFalsy()
|
||||
})
|
||||
it("should not authenticate with a non-existent token", async function () {
|
||||
const createdApiKey = await service.create(createSecretKeyFixture)
|
||||
const authenticated = await service.authenticate("some-token")
|
||||
|
||||
expect(authenticated).toBeFalsy()
|
||||
})
|
||||
it("should not authenticate with a revoked token", async function () {
|
||||
const createdApiKey = await service.create(createSecretKeyFixture)
|
||||
await service.revoke(createdApiKey.id, {
|
||||
revoked_by: "test",
|
||||
})
|
||||
const authenticated = await service.authenticate(createdApiKey.token)
|
||||
|
||||
expect(authenticated).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieving API keys", () => {
|
||||
it("should successfully return all existing api keys", async function () {
|
||||
await service.create([
|
||||
createPublishableKeyFixture,
|
||||
createSecretKeyFixture,
|
||||
])
|
||||
|
||||
const apiKeysInDatabase = await service.list()
|
||||
expect(apiKeysInDatabase).toHaveLength(2)
|
||||
})
|
||||
|
||||
it("should only return keys with matching token", async function () {
|
||||
const created = await service.create([
|
||||
createPublishableKeyFixture,
|
||||
createPublishableKeyFixture,
|
||||
])
|
||||
|
||||
const apiKeysInDatabase = await service.list({
|
||||
token: created[0].token,
|
||||
})
|
||||
expect(apiKeysInDatabase).toHaveLength(1)
|
||||
expect(apiKeysInDatabase[0].token).toEqual(created[0].token)
|
||||
})
|
||||
|
||||
it("should not return the token and salt for secret keys when listing", async function () {
|
||||
await service.create([createSecretKeyFixture])
|
||||
|
||||
const apiKeysInDatabase = await service.list()
|
||||
expect(apiKeysInDatabase).toHaveLength(1)
|
||||
expect(apiKeysInDatabase[0].token).toBeFalsy()
|
||||
expect(apiKeysInDatabase[0].salt).toBeFalsy()
|
||||
})
|
||||
|
||||
it("should return the token for publishable keys when listing", async function () {
|
||||
await service.create([createPublishableKeyFixture])
|
||||
|
||||
const apiKeysInDatabase = await service.list()
|
||||
expect(apiKeysInDatabase).toHaveLength(1)
|
||||
expect(apiKeysInDatabase[0].token).toBeTruthy()
|
||||
expect(apiKeysInDatabase[0].salt).toBeFalsy()
|
||||
})
|
||||
|
||||
it("should not return the token and salt for secret keys when listing and counting", async function () {
|
||||
await service.create([createSecretKeyFixture])
|
||||
|
||||
const [apiKeysInDatabase] = await service.listAndCount()
|
||||
expect(apiKeysInDatabase).toHaveLength(1)
|
||||
expect(apiKeysInDatabase[0].token).toBeFalsy()
|
||||
expect(apiKeysInDatabase[0].salt).toBeFalsy()
|
||||
})
|
||||
|
||||
it("should return the token for publishable keys when listing and counting", async function () {
|
||||
await service.create([createPublishableKeyFixture])
|
||||
|
||||
const [apiKeysInDatabase] = await service.listAndCount()
|
||||
expect(apiKeysInDatabase).toHaveLength(1)
|
||||
expect(apiKeysInDatabase[0].token).toBeTruthy()
|
||||
expect(apiKeysInDatabase[0].salt).toBeFalsy()
|
||||
})
|
||||
|
||||
it("should not return the token and salt for secret keys when retrieving", async function () {
|
||||
const [createdApiKey] = await service.create([createSecretKeyFixture])
|
||||
|
||||
const apiKeyInDatabase = await service.retrieve(createdApiKey.id)
|
||||
expect(apiKeyInDatabase.token).toBeFalsy()
|
||||
expect(apiKeyInDatabase.salt).toBeFalsy()
|
||||
})
|
||||
|
||||
it("should return the token for publishable keys when retrieving", async function () {
|
||||
const [createdApiKey] = await service.create([
|
||||
createPublishableKeyFixture,
|
||||
])
|
||||
|
||||
const apiKeyInDatabase = await service.retrieve(createdApiKey.id)
|
||||
expect(apiKeyInDatabase.token).toBeTruthy()
|
||||
expect(apiKeyInDatabase.salt).toBeFalsy()
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
20
packages/modules/api-key/jest.config.js
Normal file
20
packages/modules/api-key/jest.config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
moduleNameMapper: {
|
||||
"^@models": "<rootDir>/src/models",
|
||||
"^@services": "<rootDir>/src/services",
|
||||
"^@repositories": "<rootDir>/src/repositories",
|
||||
"^@types": "<rootDir>/src/types",
|
||||
},
|
||||
transform: {
|
||||
"^.+\\.[jt]s?$": [
|
||||
"ts-jest",
|
||||
{
|
||||
tsconfig: "tsconfig.spec.json",
|
||||
isolatedModules: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
testEnvironment: `node`,
|
||||
moduleFileExtensions: [`js`, `ts`],
|
||||
modulePathIgnorePatterns: ["dist/"],
|
||||
}
|
||||
12
packages/modules/api-key/mikro-orm.config.dev.ts
Normal file
12
packages/modules/api-key/mikro-orm.config.dev.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as entities from "./src/models"
|
||||
import { TSMigrationGenerator } from "@medusajs/utils"
|
||||
|
||||
module.exports = {
|
||||
entities: Object.values(entities),
|
||||
schema: "public",
|
||||
clientUrl: "postgres://postgres@localhost/medusa-api-key",
|
||||
type: "postgresql",
|
||||
migrations: {
|
||||
generator: TSMigrationGenerator,
|
||||
},
|
||||
}
|
||||
61
packages/modules/api-key/package.json
Normal file
61
packages/modules/api-key/package.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "@medusajs/api-key",
|
||||
"version": "0.1.2",
|
||||
"description": "Medusa API Key module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"bin": {
|
||||
"medusa-api-key-seed": "dist/scripts/bin/run-seed.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
"directory": "packages/api-key"
|
||||
},
|
||||
"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 --forceExit -- src/**/__tests__/**/*.ts",
|
||||
"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.44",
|
||||
"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.11",
|
||||
"@medusajs/types": "^1.11.16",
|
||||
"@medusajs/utils": "^1.11.9",
|
||||
"@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",
|
||||
"knex": "2.4.2"
|
||||
}
|
||||
}
|
||||
14
packages/modules/api-key/src/index.ts
Normal file
14
packages/modules/api-key/src/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { moduleDefinition } from "./module-definition"
|
||||
import { initializeFactory, Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
export * from "./types"
|
||||
export * from "./models"
|
||||
export * from "./services"
|
||||
|
||||
export const initialize = initializeFactory({
|
||||
moduleName: Modules.API_KEY,
|
||||
moduleDefinition,
|
||||
})
|
||||
export const runMigrations = moduleDefinition.runMigrations
|
||||
export const revertMigration = moduleDefinition.revertMigration
|
||||
export default moduleDefinition
|
||||
31
packages/modules/api-key/src/joiner-config.ts
Normal file
31
packages/modules/api-key/src/joiner-config.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { MapToConfig } from "@medusajs/utils"
|
||||
import ApiKey from "./models/api-key"
|
||||
|
||||
export const LinkableKeys: Record<string, string> = {
|
||||
api_key_id: ApiKey.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.API_KEY,
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: LinkableKeys,
|
||||
alias: [
|
||||
{
|
||||
name: ["api_key", "api_keys"],
|
||||
args: { entity: ApiKey.name },
|
||||
},
|
||||
],
|
||||
} as ModuleJoinerConfig
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"namespaces": [
|
||||
"public"
|
||||
],
|
||||
"name": "public",
|
||||
"tables": [
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"salt": {
|
||||
"name": "salt",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"redacted": {
|
||||
"name": "redacted",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"last_used_at": {
|
||||
"name": "last_used_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"created_by": {
|
||||
"name": "created_by",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"revoked_by": {
|
||||
"name": "revoked_by",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"revoked_at": {
|
||||
"name": "revoked_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "api_key",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_api_key_token_unique",
|
||||
"columnNames": [
|
||||
"token"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_api_key_token_unique\" ON \"api_key\" (token)"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_api_key_type",
|
||||
"columnNames": [
|
||||
"type"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_api_key_type\" ON \"api_key\" (type)"
|
||||
},
|
||||
{
|
||||
"keyName": "api_key_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class InitialSetup20240221144943 extends Migration {
|
||||
async up(): Promise<void> {
|
||||
this.addSql(
|
||||
'create table if not exists "api_key" ("id" text not null, "token" text not null, "salt" text not null, "redacted" text not null, "title" text not null, "type" text not null, "last_used_at" timestamptz null, "created_by" text not null, "created_at" timestamptz not null default now(), "revoked_by" text null, "revoked_at" timestamptz null, constraint "api_key_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_api_key_token_unique" ON "api_key" (token);'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE INDEX IF NOT EXISTS "IDX_api_key_type" ON "api_key" (type);'
|
||||
)
|
||||
}
|
||||
}
|
||||
86
packages/modules/api-key/src/models/api-key.ts
Normal file
86
packages/modules/api-key/src/models/api-key.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import {
|
||||
Searchable,
|
||||
createPsqlIndexStatementHelper,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Enum,
|
||||
OnInit,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
const TypeIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "api_key",
|
||||
columns: "type",
|
||||
})
|
||||
|
||||
const TokenIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "api_key",
|
||||
columns: "token",
|
||||
unique: true,
|
||||
})
|
||||
|
||||
@Entity()
|
||||
export default class ApiKey {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@TokenIndex.MikroORMIndex()
|
||||
token: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
salt: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text" })
|
||||
redacted: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text" })
|
||||
title: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@Enum({ items: ["publishable", "secret"] })
|
||||
@TypeIndex.MikroORMIndex()
|
||||
type: "publishable" | "secret"
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
last_used_at: Date | null = null
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
created_by: string
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
revoked_by: string | null = null
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
revoked_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "apk")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "apk")
|
||||
}
|
||||
}
|
||||
1
packages/modules/api-key/src/models/index.ts
Normal file
1
packages/modules/api-key/src/models/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as ApiKey } from "./api-key"
|
||||
44
packages/modules/api-key/src/module-definition.ts
Normal file
44
packages/modules/api-key/src/module-definition.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { ModuleExports } from "@medusajs/types"
|
||||
import * as ModuleServices from "@services"
|
||||
import { ApiKeyModuleService } from "@services"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import * as Models from "@models"
|
||||
import * as ModuleModels from "@models"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import * as ModuleRepositories from "@repositories"
|
||||
|
||||
const migrationScriptOptions = {
|
||||
moduleName: Modules.API_KEY,
|
||||
models: Models,
|
||||
pathToMigrations: __dirname + "/migrations",
|
||||
}
|
||||
|
||||
const runMigrations = ModulesSdkUtils.buildMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
|
||||
const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
|
||||
const containerLoader = ModulesSdkUtils.moduleContainerLoaderFactory({
|
||||
moduleModels: ModuleModels,
|
||||
moduleRepositories: ModuleRepositories,
|
||||
moduleServices: ModuleServices,
|
||||
})
|
||||
|
||||
const connectionLoader = ModulesSdkUtils.mikroOrmConnectionLoaderFactory({
|
||||
moduleName: Modules.API_KEY,
|
||||
moduleModels: Object.values(Models),
|
||||
migrationsPath: __dirname + "/migrations",
|
||||
})
|
||||
|
||||
const service = ApiKeyModuleService
|
||||
const loaders = [containerLoader, connectionLoader] as any
|
||||
|
||||
export const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
loaders,
|
||||
revertMigration,
|
||||
runMigrations,
|
||||
}
|
||||
1
packages/modules/api-key/src/repositories/index.ts
Normal file
1
packages/modules/api-key/src/repositories/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
||||
29
packages/modules/api-key/src/scripts/bin/run-seed.ts
Normal file
29
packages/modules/api-key/src/scripts/bin/run-seed.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import * as Models from "@models"
|
||||
import { EOL } from "os"
|
||||
|
||||
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-api-key-seed <filePath>`
|
||||
)
|
||||
}
|
||||
|
||||
const run = ModulesSdkUtils.buildSeedScript({
|
||||
moduleName: Modules.API_KEY,
|
||||
models: Models,
|
||||
pathToMigrations: __dirname + "/../../migrations",
|
||||
seedHandler: async ({ manager, data }) => {
|
||||
// TODO: Add seed logic
|
||||
},
|
||||
})
|
||||
await run({ path })
|
||||
})()
|
||||
5
packages/modules/api-key/src/services/__tests__/noop.ts
Normal file
5
packages/modules/api-key/src/services/__tests__/noop.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
describe("noop", function () {
|
||||
it("should run", function () {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
})
|
||||
592
packages/modules/api-key/src/services/api-key-module-service.ts
Normal file
592
packages/modules/api-key/src/services/api-key-module-service.ts
Normal file
@@ -0,0 +1,592 @@
|
||||
import crypto from "crypto"
|
||||
import util from "util"
|
||||
import {
|
||||
Context,
|
||||
DAL,
|
||||
ApiKeyTypes,
|
||||
IApiKeyModuleService,
|
||||
ModulesSdkTypes,
|
||||
InternalModuleDeclaration,
|
||||
ModuleJoinerConfig,
|
||||
FindConfig,
|
||||
FilterableApiKeyProps,
|
||||
} from "@medusajs/types"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
import { ApiKey } from "@models"
|
||||
import {
|
||||
CreateApiKeyDTO,
|
||||
RevokeApiKeyInput,
|
||||
TokenDTO,
|
||||
UpdateApiKeyInput,
|
||||
} from "@types"
|
||||
import {
|
||||
ApiKeyType,
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
isObject,
|
||||
isString,
|
||||
promiseAll,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
const scrypt = util.promisify(crypto.scrypt)
|
||||
|
||||
const generateMethodForModels = []
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
apiKeyService: ModulesSdkTypes.InternalModuleService<any>
|
||||
}
|
||||
|
||||
export default class ApiKeyModuleService<TEntity extends ApiKey = ApiKey>
|
||||
extends ModulesSdkUtils.abstractModuleServiceFactory<
|
||||
InjectedDependencies,
|
||||
ApiKeyTypes.ApiKeyDTO,
|
||||
{
|
||||
ApiKey: { dto: ApiKeyTypes.ApiKeyDTO }
|
||||
}
|
||||
>(ApiKey, generateMethodForModels, entityNameToLinkableKeysMap)
|
||||
implements IApiKeyModuleService
|
||||
{
|
||||
protected baseRepository_: DAL.RepositoryService
|
||||
protected readonly apiKeyService_: ModulesSdkTypes.InternalModuleService<TEntity>
|
||||
|
||||
constructor(
|
||||
{ baseRepository, apiKeyService }: InjectedDependencies,
|
||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
this.baseRepository_ = baseRepository
|
||||
this.apiKeyService_ = apiKeyService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return joinerConfig
|
||||
}
|
||||
|
||||
create(
|
||||
data: ApiKeyTypes.CreateApiKeyDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO[]>
|
||||
create(
|
||||
data: ApiKeyTypes.CreateApiKeyDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO>
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async create(
|
||||
data: ApiKeyTypes.CreateApiKeyDTO | ApiKeyTypes.CreateApiKeyDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO | ApiKeyTypes.ApiKeyDTO[]> {
|
||||
const [createdApiKeys, generatedTokens] = await this.create_(
|
||||
Array.isArray(data) ? data : [data],
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const serializedResponse = await this.baseRepository_.serialize<
|
||||
ApiKeyTypes.ApiKeyDTO[]
|
||||
>(createdApiKeys, {
|
||||
populate: true,
|
||||
})
|
||||
|
||||
// When creating we want to return the raw token, as this will be the only time the user will be able to take note of it for future use.
|
||||
const responseWithRawToken = serializedResponse.map((key) => ({
|
||||
...key,
|
||||
token:
|
||||
generatedTokens.find((t) => t.hashedToken === key.token)?.rawToken ??
|
||||
key.token,
|
||||
salt: undefined,
|
||||
}))
|
||||
|
||||
return Array.isArray(data) ? responseWithRawToken : responseWithRawToken[0]
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
protected async create_(
|
||||
data: ApiKeyTypes.CreateApiKeyDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], TokenDTO[]]> {
|
||||
await this.validateCreateApiKeys_(data, sharedContext)
|
||||
|
||||
const normalizedInput: CreateApiKeyDTO[] = []
|
||||
const generatedTokens: TokenDTO[] = []
|
||||
for (const key of data) {
|
||||
let tokenData: TokenDTO
|
||||
if (key.type === ApiKeyType.PUBLISHABLE) {
|
||||
tokenData = ApiKeyModuleService.generatePublishableKey()
|
||||
} else {
|
||||
tokenData = await ApiKeyModuleService.generateSecretKey()
|
||||
}
|
||||
|
||||
generatedTokens.push(tokenData)
|
||||
normalizedInput.push({
|
||||
...key,
|
||||
token: tokenData.hashedToken,
|
||||
salt: tokenData.salt,
|
||||
redacted: tokenData.redacted,
|
||||
})
|
||||
}
|
||||
|
||||
const createdApiKeys = await this.apiKeyService_.create(
|
||||
normalizedInput,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return [createdApiKeys, generatedTokens]
|
||||
}
|
||||
|
||||
async upsert(
|
||||
data: ApiKeyTypes.UpsertApiKeyDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO[]>
|
||||
async upsert(
|
||||
data: ApiKeyTypes.UpsertApiKeyDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO>
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async upsert(
|
||||
data: ApiKeyTypes.UpsertApiKeyDTO | ApiKeyTypes.UpsertApiKeyDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO | ApiKeyTypes.ApiKeyDTO[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
const forUpdate = input.filter(
|
||||
(apiKey): apiKey is UpdateApiKeyInput => !!apiKey.id
|
||||
)
|
||||
const forCreate = input.filter(
|
||||
(apiKey): apiKey is ApiKeyTypes.CreateApiKeyDTO => !apiKey.id
|
||||
)
|
||||
|
||||
const operations: Promise<ApiKeyTypes.ApiKeyDTO[]>[] = []
|
||||
|
||||
if (forCreate.length) {
|
||||
const op = async () => {
|
||||
const [createdApiKeys, generatedTokens] = await this.create_(
|
||||
forCreate,
|
||||
sharedContext
|
||||
)
|
||||
const serializedResponse = await this.baseRepository_.serialize<
|
||||
ApiKeyTypes.ApiKeyDTO[]
|
||||
>(createdApiKeys, {
|
||||
populate: true,
|
||||
})
|
||||
|
||||
return serializedResponse.map(
|
||||
(key) =>
|
||||
({
|
||||
...key,
|
||||
token:
|
||||
generatedTokens.find((t) => t.hashedToken === key.token)
|
||||
?.rawToken ?? key.token,
|
||||
salt: undefined,
|
||||
} as ApiKeyTypes.ApiKeyDTO)
|
||||
)
|
||||
}
|
||||
|
||||
operations.push(op())
|
||||
}
|
||||
|
||||
if (forUpdate.length) {
|
||||
const op = async () => {
|
||||
const updateResp = await this.update_(forUpdate, sharedContext)
|
||||
return await this.baseRepository_.serialize<ApiKeyTypes.ApiKeyDTO[]>(
|
||||
updateResp
|
||||
)
|
||||
}
|
||||
|
||||
operations.push(op())
|
||||
}
|
||||
|
||||
const result = (await promiseAll(operations)).flat()
|
||||
return Array.isArray(data) ? result : result[0]
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
data: ApiKeyTypes.UpdateApiKeyDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO>
|
||||
async update(
|
||||
selector: FilterableApiKeyProps,
|
||||
data: ApiKeyTypes.UpdateApiKeyDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO[]>
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async update(
|
||||
idOrSelector: string | FilterableApiKeyProps,
|
||||
data: ApiKeyTypes.UpdateApiKeyDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO[] | ApiKeyTypes.ApiKeyDTO> {
|
||||
let normalizedInput = await this.normalizeUpdateInput_<UpdateApiKeyInput>(
|
||||
idOrSelector,
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const updatedApiKeys = await this.update_(normalizedInput, sharedContext)
|
||||
|
||||
const serializedResponse = await this.baseRepository_.serialize<
|
||||
ApiKeyTypes.ApiKeyDTO[]
|
||||
>(updatedApiKeys.map(omitToken), {
|
||||
populate: true,
|
||||
})
|
||||
|
||||
return isString(idOrSelector) ? serializedResponse[0] : serializedResponse
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
protected async update_(
|
||||
normalizedInput: UpdateApiKeyInput[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
const updateRequest = normalizedInput.map((k) => ({
|
||||
id: k.id,
|
||||
title: k.title,
|
||||
}))
|
||||
|
||||
const updatedApiKeys = await this.apiKeyService_.update(
|
||||
updateRequest,
|
||||
sharedContext
|
||||
)
|
||||
return updatedApiKeys
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async retrieve(
|
||||
id: string,
|
||||
config?: FindConfig<ApiKeyTypes.ApiKeyDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO> {
|
||||
const apiKey = await this.apiKeyService_.retrieve(id, config, sharedContext)
|
||||
|
||||
return await this.baseRepository_.serialize<ApiKeyTypes.ApiKeyDTO>(
|
||||
omitToken(apiKey),
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async list(
|
||||
filters?: ApiKeyTypes.FilterableApiKeyProps,
|
||||
config?: FindConfig<ApiKeyTypes.ApiKeyDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO[]> {
|
||||
const apiKeys = await this.apiKeyService_.list(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize<ApiKeyTypes.ApiKeyDTO[]>(
|
||||
apiKeys.map(omitToken),
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCount(
|
||||
filters?: ApiKeyTypes.FilterableApiKeyProps,
|
||||
config?: FindConfig<ApiKeyTypes.ApiKeyDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<[ApiKeyTypes.ApiKeyDTO[], number]> {
|
||||
const [apiKeys, count] = await this.apiKeyService_.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return [
|
||||
await this.baseRepository_.serialize<ApiKeyTypes.ApiKeyDTO[]>(
|
||||
apiKeys.map(omitToken),
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
),
|
||||
count,
|
||||
]
|
||||
}
|
||||
|
||||
async revoke(
|
||||
id: string,
|
||||
data: ApiKeyTypes.RevokeApiKeyDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO>
|
||||
async revoke(
|
||||
selector: FilterableApiKeyProps,
|
||||
data: ApiKeyTypes.RevokeApiKeyDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO[]>
|
||||
@InjectManager("baseRepository_")
|
||||
async revoke(
|
||||
idOrSelector: string | FilterableApiKeyProps,
|
||||
data: ApiKeyTypes.RevokeApiKeyDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO[] | ApiKeyTypes.ApiKeyDTO> {
|
||||
const normalizedInput = await this.normalizeUpdateInput_<RevokeApiKeyInput>(
|
||||
idOrSelector,
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
const revokedApiKeys = await this.revoke_(normalizedInput, sharedContext)
|
||||
|
||||
const serializedResponse = await this.baseRepository_.serialize<
|
||||
ApiKeyTypes.ApiKeyDTO[]
|
||||
>(revokedApiKeys.map(omitToken), {
|
||||
populate: true,
|
||||
})
|
||||
|
||||
return isString(idOrSelector) ? serializedResponse[0] : serializedResponse
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async revoke_(
|
||||
normalizedInput: RevokeApiKeyInput[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
await this.validateRevokeApiKeys_(normalizedInput)
|
||||
|
||||
const updateRequest = normalizedInput.map((k) => {
|
||||
const revokedAt = new Date()
|
||||
if (k.revoke_in && k.revoke_in > 0) {
|
||||
revokedAt.setSeconds(revokedAt.getSeconds() + k.revoke_in)
|
||||
}
|
||||
|
||||
return {
|
||||
id: k.id,
|
||||
revoked_at: revokedAt,
|
||||
revoked_by: k.revoked_by,
|
||||
}
|
||||
})
|
||||
|
||||
const revokedApiKeys = await this.apiKeyService_.update(
|
||||
updateRequest,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return revokedApiKeys
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async authenticate(
|
||||
token: string,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ApiKeyTypes.ApiKeyDTO | false> {
|
||||
const result = await this.authenticate_(token, sharedContext)
|
||||
if (!result) {
|
||||
return false
|
||||
}
|
||||
|
||||
const serialized =
|
||||
await this.baseRepository_.serialize<ApiKeyTypes.ApiKeyDTO>(result, {
|
||||
populate: true,
|
||||
})
|
||||
|
||||
return serialized
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
protected async authenticate_(
|
||||
token: string,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ApiKey | false> {
|
||||
// Since we only allow up to 2 active tokens, getitng the list and checking each token isn't an issue.
|
||||
// We can always filter on the redacted key if we add support for an arbitrary number of tokens.
|
||||
const secretKeys = await this.apiKeyService_.list(
|
||||
{
|
||||
type: ApiKeyType.SECRET,
|
||||
// If the revoke date is set in the future, it means the key is still valid.
|
||||
$or: [
|
||||
{ revoked_at: { $eq: null } },
|
||||
{ revoked_at: { $gt: new Date() } },
|
||||
],
|
||||
},
|
||||
{ take: null },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const matches = await promiseAll(
|
||||
secretKeys.map(async (dbKey) => {
|
||||
const hashedInput = await ApiKeyModuleService.calculateHash(
|
||||
token,
|
||||
dbKey.salt
|
||||
)
|
||||
if (hashedInput === dbKey.token) {
|
||||
return dbKey
|
||||
}
|
||||
|
||||
return undefined
|
||||
})
|
||||
)
|
||||
|
||||
const matchedKeys = matches.filter((match) => !!match)
|
||||
if (!matchedKeys.length) {
|
||||
return false
|
||||
}
|
||||
return matchedKeys[0]!
|
||||
}
|
||||
|
||||
protected async validateCreateApiKeys_(
|
||||
data: ApiKeyTypes.CreateApiKeyDTO[],
|
||||
sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
if (!data.length) {
|
||||
return
|
||||
}
|
||||
|
||||
// There can only be 2 secret keys at most, and one has to be with a revoked_at date set, so only 1 can be newly created.
|
||||
const secretKeysToCreate = data.filter((k) => k.type === ApiKeyType.SECRET)
|
||||
if (!secretKeysToCreate.length) {
|
||||
return
|
||||
}
|
||||
|
||||
if (secretKeysToCreate.length > 1) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`You can only create one secret key at a time. You tried to create ${secretKeysToCreate.length} secret keys.`
|
||||
)
|
||||
}
|
||||
|
||||
// There already is a key that is not set to expire/or it hasn't expired
|
||||
const dbSecretKeys = await this.apiKeyService_.list(
|
||||
{
|
||||
type: ApiKeyType.SECRET,
|
||||
$or: [
|
||||
{ revoked_at: { $eq: null } },
|
||||
{ revoked_at: { $gt: new Date() } },
|
||||
],
|
||||
},
|
||||
{ take: null },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (dbSecretKeys.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`You can only have one active secret key a time. Revoke or delete your existing key before creating a new one.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
protected async normalizeUpdateInput_<T>(
|
||||
idOrSelector: string | FilterableApiKeyProps,
|
||||
data: Omit<T, "id">,
|
||||
sharedContext: Context = {}
|
||||
): Promise<T[]> {
|
||||
let normalizedInput: T[] = []
|
||||
if (isString(idOrSelector)) {
|
||||
normalizedInput = [{ id: idOrSelector, ...data } as T]
|
||||
}
|
||||
|
||||
if (isObject(idOrSelector)) {
|
||||
const apiKeys = await this.apiKeyService_.list(
|
||||
idOrSelector,
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
normalizedInput = apiKeys.map(
|
||||
(apiKey) =>
|
||||
({
|
||||
id: apiKey.id,
|
||||
...data,
|
||||
} as T)
|
||||
)
|
||||
}
|
||||
|
||||
return normalizedInput
|
||||
}
|
||||
|
||||
protected async validateRevokeApiKeys_(
|
||||
data: RevokeApiKeyInput[],
|
||||
sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
if (!data.length) {
|
||||
return
|
||||
}
|
||||
|
||||
if (data.some((k) => !k.id)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`You must provide an api key id field when revoking a key.`
|
||||
)
|
||||
}
|
||||
|
||||
if (data.some((k) => !k.revoked_by)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`You must provide a revoked_by field when revoking a key.`
|
||||
)
|
||||
}
|
||||
|
||||
const revokedApiKeys = await this.apiKeyService_.list(
|
||||
{
|
||||
id: data.map((k) => k.id),
|
||||
type: ApiKeyType.SECRET,
|
||||
revoked_at: { $ne: null },
|
||||
},
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (revokedApiKeys.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`There are ${revokedApiKeys.length} secret keys that are already revoked.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// These are public keys, so there is no point hashing them.
|
||||
protected static generatePublishableKey(): TokenDTO {
|
||||
const token = "pk_" + crypto.randomBytes(32).toString("hex")
|
||||
|
||||
return {
|
||||
rawToken: token,
|
||||
hashedToken: token,
|
||||
salt: "",
|
||||
redacted: redactKey(token),
|
||||
}
|
||||
}
|
||||
|
||||
protected static async generateSecretKey(): Promise<TokenDTO> {
|
||||
const token = "sk_" + crypto.randomBytes(32).toString("hex")
|
||||
const salt = crypto.randomBytes(16).toString("hex")
|
||||
const hashed = await this.calculateHash(token, salt)
|
||||
|
||||
return {
|
||||
rawToken: token,
|
||||
hashedToken: hashed,
|
||||
salt,
|
||||
redacted: redactKey(token),
|
||||
}
|
||||
}
|
||||
|
||||
protected static async calculateHash(
|
||||
token: string,
|
||||
salt: string
|
||||
): Promise<string> {
|
||||
return ((await scrypt(token, salt, 64)) as Buffer).toString("hex")
|
||||
}
|
||||
}
|
||||
|
||||
// We are mutating the object here as what microORM relies on non-enumerable fields for serialization, among other things.
|
||||
const omitToken = (
|
||||
// We have to make salt optional before deleting it (and we do want it required in the DB)
|
||||
key: Omit<ApiKey, "salt"> & { salt?: string }
|
||||
): Omit<ApiKey, "salt"> => {
|
||||
key.token = key.type === ApiKeyType.SECRET ? "" : key.token
|
||||
delete key.salt
|
||||
return key
|
||||
}
|
||||
|
||||
const redactKey = (key: string): string => {
|
||||
return [key.slice(0, 6), key.slice(-3)].join("***")
|
||||
}
|
||||
1
packages/modules/api-key/src/services/index.ts
Normal file
1
packages/modules/api-key/src/services/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as ApiKeyModuleService } from "./api-key-module-service"
|
||||
26
packages/modules/api-key/src/types/index.ts
Normal file
26
packages/modules/api-key/src/types/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ApiKeyType, RevokeApiKeyDTO, UpdateApiKeyDTO } from "@medusajs/types"
|
||||
import { IEventBusModuleService, Logger } from "@medusajs/types"
|
||||
|
||||
export type InitializeModuleInjectableDependencies = {
|
||||
logger?: Logger
|
||||
eventBusService?: IEventBusModuleService
|
||||
}
|
||||
|
||||
export type CreateApiKeyDTO = {
|
||||
token: string
|
||||
salt: string
|
||||
redacted: string
|
||||
title: string
|
||||
type: ApiKeyType
|
||||
created_by: string
|
||||
}
|
||||
|
||||
export type TokenDTO = {
|
||||
rawToken: string
|
||||
hashedToken: string
|
||||
salt: string
|
||||
redacted: string
|
||||
}
|
||||
|
||||
export type UpdateApiKeyInput = UpdateApiKeyDTO & { id: string }
|
||||
export type RevokeApiKeyInput = RevokeApiKeyDTO & { id: string }
|
||||
37
packages/modules/api-key/tsconfig.json
Normal file
37
packages/modules/api-key/tsconfig.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"./src/**/__tests__",
|
||||
"./src/**/__mocks__",
|
||||
"./src/**/__fixtures__",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
8
packages/modules/api-key/tsconfig.spec.json
Normal file
8
packages/modules/api-key/tsconfig.spec.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src", "integration-tests"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user