@medusajs/api-key 0.1.0 → 0.1.1-next-20240319121007
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/joiner-config.js +10 -2
- package/dist/migrations/InitialSetup20240221144943.d.ts +4 -0
- package/dist/migrations/InitialSetup20240221144943.js +12 -0
- package/dist/models/api-key.d.ts +9 -1
- package/dist/models/api-key.js +55 -6
- package/dist/services/api-key-module-service.d.ts +23 -8
- package/dist/services/api-key-module-service.js +290 -19
- package/dist/types/index.d.ts +21 -0
- package/package.json +7 -7
package/dist/joiner-config.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.joinerConfig = exports.entityNameToLinkableKeysMap = exports.LinkableKeys = void 0;
|
|
4
7
|
const modules_sdk_1 = require("@medusajs/modules-sdk");
|
|
5
|
-
|
|
8
|
+
const api_key_1 = __importDefault(require("./models/api-key"));
|
|
6
9
|
exports.LinkableKeys = {};
|
|
7
10
|
const entityLinkableKeysMap = {};
|
|
8
11
|
Object.entries(exports.LinkableKeys).forEach(([key, value]) => {
|
|
@@ -17,5 +20,10 @@ exports.joinerConfig = {
|
|
|
17
20
|
serviceName: modules_sdk_1.Modules.API_KEY,
|
|
18
21
|
primaryKeys: ["id"],
|
|
19
22
|
linkableKeys: exports.LinkableKeys,
|
|
20
|
-
alias: [
|
|
23
|
+
alias: [
|
|
24
|
+
{
|
|
25
|
+
name: ["api_key", "api_keys"],
|
|
26
|
+
args: { entity: api_key_1.default.name },
|
|
27
|
+
},
|
|
28
|
+
],
|
|
21
29
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InitialSetup20240221144943 = void 0;
|
|
4
|
+
const migrations_1 = require("@mikro-orm/migrations");
|
|
5
|
+
class InitialSetup20240221144943 extends migrations_1.Migration {
|
|
6
|
+
async up() {
|
|
7
|
+
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"));');
|
|
8
|
+
this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_api_key_token_unique" ON "api_key" (token);');
|
|
9
|
+
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_api_key_type" ON "api_key" (type);');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.InitialSetup20240221144943 = InitialSetup20240221144943;
|
package/dist/models/api-key.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
export default class ApiKey {
|
|
2
2
|
id: string;
|
|
3
|
+
token: string;
|
|
4
|
+
salt: string;
|
|
5
|
+
redacted: string;
|
|
6
|
+
title: string;
|
|
7
|
+
type: "publishable" | "secret";
|
|
8
|
+
last_used_at: Date | null;
|
|
9
|
+
created_by: string;
|
|
3
10
|
created_at: Date;
|
|
4
|
-
|
|
11
|
+
revoked_by: string | null;
|
|
12
|
+
revoked_at: Date | null;
|
|
5
13
|
onCreate(): void;
|
|
6
14
|
onInit(): void;
|
|
7
15
|
}
|
package/dist/models/api-key.js
CHANGED
|
@@ -11,8 +11,21 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const utils_1 = require("@medusajs/utils");
|
|
13
13
|
const core_1 = require("@mikro-orm/core");
|
|
14
|
-
|
|
14
|
+
const TypeIndex = (0, utils_1.createPsqlIndexStatementHelper)({
|
|
15
|
+
tableName: "api_key",
|
|
16
|
+
columns: "type",
|
|
17
|
+
});
|
|
18
|
+
const TokenIndex = (0, utils_1.createPsqlIndexStatementHelper)({
|
|
19
|
+
tableName: "api_key",
|
|
20
|
+
columns: "token",
|
|
21
|
+
unique: true,
|
|
22
|
+
});
|
|
15
23
|
let ApiKey = class ApiKey {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.last_used_at = null;
|
|
26
|
+
this.revoked_by = null;
|
|
27
|
+
this.revoked_at = null;
|
|
28
|
+
}
|
|
16
29
|
onCreate() {
|
|
17
30
|
this.id = (0, utils_1.generateEntityId)(this.id, "apk");
|
|
18
31
|
}
|
|
@@ -24,6 +37,40 @@ __decorate([
|
|
|
24
37
|
(0, core_1.PrimaryKey)({ columnType: "text" }),
|
|
25
38
|
__metadata("design:type", String)
|
|
26
39
|
], ApiKey.prototype, "id", void 0);
|
|
40
|
+
__decorate([
|
|
41
|
+
(0, core_1.Property)({ columnType: "text" }),
|
|
42
|
+
TokenIndex.MikroORMIndex(),
|
|
43
|
+
__metadata("design:type", String)
|
|
44
|
+
], ApiKey.prototype, "token", void 0);
|
|
45
|
+
__decorate([
|
|
46
|
+
(0, core_1.Property)({ columnType: "text" }),
|
|
47
|
+
__metadata("design:type", String)
|
|
48
|
+
], ApiKey.prototype, "salt", void 0);
|
|
49
|
+
__decorate([
|
|
50
|
+
(0, core_1.Property)({ columnType: "text" }),
|
|
51
|
+
__metadata("design:type", String)
|
|
52
|
+
], ApiKey.prototype, "redacted", void 0);
|
|
53
|
+
__decorate([
|
|
54
|
+
(0, core_1.Property)({ columnType: "text" }),
|
|
55
|
+
__metadata("design:type", String)
|
|
56
|
+
], ApiKey.prototype, "title", void 0);
|
|
57
|
+
__decorate([
|
|
58
|
+
(0, core_1.Property)({ columnType: "text" }),
|
|
59
|
+
(0, core_1.Enum)({ items: ["publishable", "secret"] }),
|
|
60
|
+
TypeIndex.MikroORMIndex(),
|
|
61
|
+
__metadata("design:type", String)
|
|
62
|
+
], ApiKey.prototype, "type", void 0);
|
|
63
|
+
__decorate([
|
|
64
|
+
(0, core_1.Property)({
|
|
65
|
+
columnType: "timestamptz",
|
|
66
|
+
nullable: true,
|
|
67
|
+
}),
|
|
68
|
+
__metadata("design:type", Object)
|
|
69
|
+
], ApiKey.prototype, "last_used_at", void 0);
|
|
70
|
+
__decorate([
|
|
71
|
+
(0, core_1.Property)({ columnType: "text" }),
|
|
72
|
+
__metadata("design:type", String)
|
|
73
|
+
], ApiKey.prototype, "created_by", void 0);
|
|
27
74
|
__decorate([
|
|
28
75
|
(0, core_1.Property)({
|
|
29
76
|
onCreate: () => new Date(),
|
|
@@ -32,15 +79,17 @@ __decorate([
|
|
|
32
79
|
}),
|
|
33
80
|
__metadata("design:type", Date)
|
|
34
81
|
], ApiKey.prototype, "created_at", void 0);
|
|
82
|
+
__decorate([
|
|
83
|
+
(0, core_1.Property)({ columnType: "text", nullable: true }),
|
|
84
|
+
__metadata("design:type", Object)
|
|
85
|
+
], ApiKey.prototype, "revoked_by", void 0);
|
|
35
86
|
__decorate([
|
|
36
87
|
(0, core_1.Property)({
|
|
37
|
-
onCreate: () => new Date(),
|
|
38
|
-
onUpdate: () => new Date(),
|
|
39
88
|
columnType: "timestamptz",
|
|
40
|
-
|
|
89
|
+
nullable: true,
|
|
41
90
|
}),
|
|
42
|
-
__metadata("design:type",
|
|
43
|
-
], ApiKey.prototype, "
|
|
91
|
+
__metadata("design:type", Object)
|
|
92
|
+
], ApiKey.prototype, "revoked_at", void 0);
|
|
44
93
|
__decorate([
|
|
45
94
|
(0, core_1.BeforeCreate)(),
|
|
46
95
|
__metadata("design:type", Function),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Context, DAL, ApiKeyTypes, IApiKeyModuleService, ModulesSdkTypes, InternalModuleDeclaration, ModuleJoinerConfig } from "@medusajs/types";
|
|
2
|
-
import { ModulesSdkUtils } from "@medusajs/utils";
|
|
1
|
+
import { Context, DAL, ApiKeyTypes, IApiKeyModuleService, ModulesSdkTypes, InternalModuleDeclaration, ModuleJoinerConfig, FindConfig, FilterableApiKeyProps } from "@medusajs/types";
|
|
3
2
|
import { ApiKey } from "../models";
|
|
3
|
+
import { RevokeApiKeyInput, TokenDTO, UpdateApiKeyInput } from "../types";
|
|
4
|
+
import { ModulesSdkUtils } from "@medusajs/utils";
|
|
4
5
|
type InjectedDependencies = {
|
|
5
6
|
baseRepository: DAL.RepositoryService;
|
|
6
7
|
apiKeyService: ModulesSdkTypes.InternalModuleService<any>;
|
|
@@ -18,11 +19,25 @@ export default class ApiKeyModuleService<TEntity extends ApiKey = ApiKey> extend
|
|
|
18
19
|
__joinerConfig(): ModuleJoinerConfig;
|
|
19
20
|
create(data: ApiKeyTypes.CreateApiKeyDTO[], sharedContext?: Context): Promise<ApiKeyTypes.ApiKeyDTO[]>;
|
|
20
21
|
create(data: ApiKeyTypes.CreateApiKeyDTO, sharedContext?: Context): Promise<ApiKeyTypes.ApiKeyDTO>;
|
|
21
|
-
protected create_(data: ApiKeyTypes.CreateApiKeyDTO
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
protected create_(data: ApiKeyTypes.CreateApiKeyDTO[], sharedContext?: Context): Promise<[TEntity[], TokenDTO[]]>;
|
|
23
|
+
upsert(data: ApiKeyTypes.UpsertApiKeyDTO[], sharedContext?: Context): Promise<ApiKeyTypes.ApiKeyDTO[]>;
|
|
24
|
+
upsert(data: ApiKeyTypes.UpsertApiKeyDTO, sharedContext?: Context): Promise<ApiKeyTypes.ApiKeyDTO>;
|
|
25
|
+
update(id: string, data: ApiKeyTypes.UpdateApiKeyDTO, sharedContext?: Context): Promise<ApiKeyTypes.ApiKeyDTO>;
|
|
26
|
+
update(selector: FilterableApiKeyProps, data: ApiKeyTypes.UpdateApiKeyDTO, sharedContext?: Context): Promise<ApiKeyTypes.ApiKeyDTO[]>;
|
|
27
|
+
protected update_(normalizedInput: UpdateApiKeyInput[], sharedContext?: Context): Promise<TEntity[]>;
|
|
28
|
+
retrieve(id: string, config?: FindConfig<ApiKeyTypes.ApiKeyDTO>, sharedContext?: Context): Promise<ApiKeyTypes.ApiKeyDTO>;
|
|
29
|
+
list(filters?: ApiKeyTypes.FilterableApiKeyProps, config?: FindConfig<ApiKeyTypes.ApiKeyDTO>, sharedContext?: Context): Promise<ApiKeyTypes.ApiKeyDTO[]>;
|
|
30
|
+
listAndCount(filters?: ApiKeyTypes.FilterableApiKeyProps, config?: FindConfig<ApiKeyTypes.ApiKeyDTO>, sharedContext?: Context): Promise<[ApiKeyTypes.ApiKeyDTO[], number]>;
|
|
31
|
+
revoke(id: string, data: ApiKeyTypes.RevokeApiKeyDTO, sharedContext?: Context): Promise<ApiKeyTypes.ApiKeyDTO>;
|
|
32
|
+
revoke(selector: FilterableApiKeyProps, data: ApiKeyTypes.RevokeApiKeyDTO, sharedContext?: Context): Promise<ApiKeyTypes.ApiKeyDTO[]>;
|
|
33
|
+
revoke_(normalizedInput: RevokeApiKeyInput[], sharedContext?: Context): Promise<TEntity[]>;
|
|
34
|
+
authenticate(token: string, sharedContext?: Context): Promise<ApiKeyTypes.ApiKeyDTO | false>;
|
|
35
|
+
protected authenticate_(token: string, sharedContext?: Context): Promise<ApiKey | false>;
|
|
36
|
+
protected validateCreateApiKeys_(data: ApiKeyTypes.CreateApiKeyDTO[], sharedContext?: Context): Promise<void>;
|
|
37
|
+
protected normalizeUpdateInput_<T>(idOrSelector: string | FilterableApiKeyProps, data: Omit<T, "id">, sharedContext?: Context): Promise<T[]>;
|
|
38
|
+
protected validateRevokeApiKeys_(data: RevokeApiKeyInput[], sharedContext?: Context): Promise<void>;
|
|
39
|
+
protected static generatePublishableKey(): TokenDTO;
|
|
40
|
+
protected static generateSecretKey(): Promise<TokenDTO>;
|
|
41
|
+
protected static calculateHash(token: string, salt: string): Promise<string>;
|
|
27
42
|
}
|
|
28
43
|
export {};
|
|
@@ -11,10 +11,17 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
11
11
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
12
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
13
|
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
14
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
const
|
|
18
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
19
|
+
const util_1 = __importDefault(require("util"));
|
|
20
|
+
const types_1 = require("@medusajs/types");
|
|
16
21
|
const joiner_config_1 = require("../joiner-config");
|
|
17
22
|
const _models_1 = require("../models");
|
|
23
|
+
const utils_1 = require("@medusajs/utils");
|
|
24
|
+
const scrypt = util_1.default.promisify(crypto_1.default.scrypt);
|
|
18
25
|
const generateMethodForModels = [];
|
|
19
26
|
class ApiKeyModuleService extends utils_1.ModulesSdkUtils.abstractModuleServiceFactory(_models_1.ApiKey, generateMethodForModels, joiner_config_1.entityNameToLinkableKeysMap) {
|
|
20
27
|
constructor({ baseRepository, apiKeyService }, moduleDeclaration) {
|
|
@@ -28,30 +35,244 @@ class ApiKeyModuleService extends utils_1.ModulesSdkUtils.abstractModuleServiceF
|
|
|
28
35
|
return joiner_config_1.joinerConfig;
|
|
29
36
|
}
|
|
30
37
|
async create(data, sharedContext = {}) {
|
|
31
|
-
const createdApiKeys = await this.create_(data, sharedContext);
|
|
32
|
-
|
|
38
|
+
const [createdApiKeys, generatedTokens] = await this.create_(Array.isArray(data) ? data : [data], sharedContext);
|
|
39
|
+
const serializedResponse = await this.baseRepository_.serialize(createdApiKeys, {
|
|
33
40
|
populate: true,
|
|
34
41
|
});
|
|
42
|
+
// 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.
|
|
43
|
+
const responseWithRawToken = serializedResponse.map((key) => ({
|
|
44
|
+
...key,
|
|
45
|
+
token: generatedTokens.find((t) => t.hashedToken === key.token)?.rawToken ??
|
|
46
|
+
key.token,
|
|
47
|
+
salt: undefined,
|
|
48
|
+
}));
|
|
49
|
+
return Array.isArray(data) ? responseWithRawToken : responseWithRawToken[0];
|
|
35
50
|
}
|
|
36
51
|
async create_(data, sharedContext = {}) {
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
52
|
+
await this.validateCreateApiKeys_(data, sharedContext);
|
|
53
|
+
const normalizedInput = [];
|
|
54
|
+
const generatedTokens = [];
|
|
55
|
+
for (const key of data) {
|
|
56
|
+
let tokenData;
|
|
57
|
+
if (key.type === utils_1.ApiKeyType.PUBLISHABLE) {
|
|
58
|
+
tokenData = ApiKeyModuleService.generatePublishableKey();
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
tokenData = await ApiKeyModuleService.generateSecretKey();
|
|
62
|
+
}
|
|
63
|
+
generatedTokens.push(tokenData);
|
|
64
|
+
normalizedInput.push({
|
|
65
|
+
...key,
|
|
66
|
+
token: tokenData.hashedToken,
|
|
67
|
+
salt: tokenData.salt,
|
|
68
|
+
redacted: tokenData.redacted,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const createdApiKeys = await this.apiKeyService_.create(normalizedInput, sharedContext);
|
|
72
|
+
return [createdApiKeys, generatedTokens];
|
|
73
|
+
}
|
|
74
|
+
async upsert(data, sharedContext = {}) {
|
|
75
|
+
const input = Array.isArray(data) ? data : [data];
|
|
76
|
+
const forUpdate = input.filter((apiKey) => !!apiKey.id);
|
|
77
|
+
const forCreate = input.filter((apiKey) => !apiKey.id);
|
|
78
|
+
const operations = [];
|
|
79
|
+
if (forCreate.length) {
|
|
80
|
+
const op = async () => {
|
|
81
|
+
const [createdApiKeys, generatedTokens] = await this.create_(forCreate, sharedContext);
|
|
82
|
+
const serializedResponse = await this.baseRepository_.serialize(createdApiKeys, {
|
|
83
|
+
populate: true,
|
|
84
|
+
});
|
|
85
|
+
return serializedResponse.map((key) => ({
|
|
86
|
+
...key,
|
|
87
|
+
token: generatedTokens.find((t) => t.hashedToken === key.token)
|
|
88
|
+
?.rawToken ?? key.token,
|
|
89
|
+
salt: undefined,
|
|
90
|
+
}));
|
|
91
|
+
};
|
|
92
|
+
operations.push(op());
|
|
93
|
+
}
|
|
94
|
+
if (forUpdate.length) {
|
|
95
|
+
const op = async () => {
|
|
96
|
+
const updateResp = await this.update_(forUpdate, sharedContext);
|
|
97
|
+
return await this.baseRepository_.serialize(updateResp);
|
|
98
|
+
};
|
|
99
|
+
operations.push(op());
|
|
100
|
+
}
|
|
101
|
+
const result = (await (0, utils_1.promiseAll)(operations)).flat();
|
|
102
|
+
return Array.isArray(data) ? result : result[0];
|
|
40
103
|
}
|
|
41
|
-
async update(data, sharedContext = {}) {
|
|
42
|
-
|
|
43
|
-
|
|
104
|
+
async update(idOrSelector, data, sharedContext = {}) {
|
|
105
|
+
let normalizedInput = await this.normalizeUpdateInput_(idOrSelector, data, sharedContext);
|
|
106
|
+
const updatedApiKeys = await this.update_(normalizedInput, sharedContext);
|
|
107
|
+
const serializedResponse = await this.baseRepository_.serialize(updatedApiKeys.map(omitToken), {
|
|
44
108
|
populate: true,
|
|
45
109
|
});
|
|
110
|
+
return (0, utils_1.isString)(idOrSelector) ? serializedResponse[0] : serializedResponse;
|
|
46
111
|
}
|
|
47
|
-
async update_(
|
|
48
|
-
|
|
112
|
+
async update_(normalizedInput, sharedContext = {}) {
|
|
113
|
+
const updateRequest = normalizedInput.map((k) => ({
|
|
114
|
+
id: k.id,
|
|
115
|
+
title: k.title,
|
|
116
|
+
}));
|
|
117
|
+
const updatedApiKeys = await this.apiKeyService_.update(updateRequest, sharedContext);
|
|
118
|
+
return updatedApiKeys;
|
|
49
119
|
}
|
|
50
|
-
async
|
|
51
|
-
|
|
120
|
+
async retrieve(id, config, sharedContext) {
|
|
121
|
+
const apiKey = await this.apiKeyService_.retrieve(id, config, sharedContext);
|
|
122
|
+
return await this.baseRepository_.serialize(omitToken(apiKey), {
|
|
123
|
+
populate: true,
|
|
124
|
+
});
|
|
52
125
|
}
|
|
53
|
-
|
|
54
|
-
|
|
126
|
+
async list(filters, config, sharedContext) {
|
|
127
|
+
const apiKeys = await this.apiKeyService_.list(filters, config, sharedContext);
|
|
128
|
+
return await this.baseRepository_.serialize(apiKeys.map(omitToken), {
|
|
129
|
+
populate: true,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
async listAndCount(filters, config, sharedContext) {
|
|
133
|
+
const [apiKeys, count] = await this.apiKeyService_.listAndCount(filters, config, sharedContext);
|
|
134
|
+
return [
|
|
135
|
+
await this.baseRepository_.serialize(apiKeys.map(omitToken), {
|
|
136
|
+
populate: true,
|
|
137
|
+
}),
|
|
138
|
+
count,
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
async revoke(idOrSelector, data, sharedContext = {}) {
|
|
142
|
+
const normalizedInput = await this.normalizeUpdateInput_(idOrSelector, data, sharedContext);
|
|
143
|
+
const revokedApiKeys = await this.revoke_(normalizedInput, sharedContext);
|
|
144
|
+
const serializedResponse = await this.baseRepository_.serialize(revokedApiKeys.map(omitToken), {
|
|
145
|
+
populate: true,
|
|
146
|
+
});
|
|
147
|
+
return (0, utils_1.isString)(idOrSelector) ? serializedResponse[0] : serializedResponse;
|
|
148
|
+
}
|
|
149
|
+
async revoke_(normalizedInput, sharedContext = {}) {
|
|
150
|
+
await this.validateRevokeApiKeys_(normalizedInput);
|
|
151
|
+
const updateRequest = normalizedInput.map((k) => {
|
|
152
|
+
const revokedAt = new Date();
|
|
153
|
+
if (k.revoke_in && k.revoke_in > 0) {
|
|
154
|
+
revokedAt.setSeconds(revokedAt.getSeconds() + k.revoke_in);
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
id: k.id,
|
|
158
|
+
revoked_at: revokedAt,
|
|
159
|
+
revoked_by: k.revoked_by,
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
const revokedApiKeys = await this.apiKeyService_.update(updateRequest, sharedContext);
|
|
163
|
+
return revokedApiKeys;
|
|
164
|
+
}
|
|
165
|
+
async authenticate(token, sharedContext = {}) {
|
|
166
|
+
const result = await this.authenticate_(token, sharedContext);
|
|
167
|
+
if (!result) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const serialized = await this.baseRepository_.serialize(result, {
|
|
171
|
+
populate: true,
|
|
172
|
+
});
|
|
173
|
+
return serialized;
|
|
174
|
+
}
|
|
175
|
+
async authenticate_(token, sharedContext = {}) {
|
|
176
|
+
// Since we only allow up to 2 active tokens, getitng the list and checking each token isn't an issue.
|
|
177
|
+
// We can always filter on the redacted key if we add support for an arbitrary number of tokens.
|
|
178
|
+
const secretKeys = await this.apiKeyService_.list({
|
|
179
|
+
type: utils_1.ApiKeyType.SECRET,
|
|
180
|
+
// If the revoke date is set in the future, it means the key is still valid.
|
|
181
|
+
$or: [
|
|
182
|
+
{ revoked_at: { $eq: null } },
|
|
183
|
+
{ revoked_at: { $gt: new Date() } },
|
|
184
|
+
],
|
|
185
|
+
}, { take: null }, sharedContext);
|
|
186
|
+
const matches = await (0, utils_1.promiseAll)(secretKeys.map(async (dbKey) => {
|
|
187
|
+
const hashedInput = await ApiKeyModuleService.calculateHash(token, dbKey.salt);
|
|
188
|
+
if (hashedInput === dbKey.token) {
|
|
189
|
+
return dbKey;
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
192
|
+
}));
|
|
193
|
+
const matchedKeys = matches.filter((match) => !!match);
|
|
194
|
+
if (!matchedKeys.length) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
return matchedKeys[0];
|
|
198
|
+
}
|
|
199
|
+
async validateCreateApiKeys_(data, sharedContext = {}) {
|
|
200
|
+
if (!data.length) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// 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.
|
|
204
|
+
const secretKeysToCreate = data.filter((k) => k.type === utils_1.ApiKeyType.SECRET);
|
|
205
|
+
if (secretKeysToCreate.length > 1) {
|
|
206
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `You can only create one secret key at a time. You tried to create ${secretKeysToCreate.length} secret keys.`);
|
|
207
|
+
}
|
|
208
|
+
// There already is a key that is not set to expire/or it hasn't expired
|
|
209
|
+
const dbSecretKeys = await this.apiKeyService_.list({
|
|
210
|
+
type: utils_1.ApiKeyType.SECRET,
|
|
211
|
+
$or: [
|
|
212
|
+
{ revoked_at: { $eq: null } },
|
|
213
|
+
{ revoked_at: { $gt: new Date() } },
|
|
214
|
+
],
|
|
215
|
+
}, { take: null }, sharedContext);
|
|
216
|
+
if (dbSecretKeys.length) {
|
|
217
|
+
throw new utils_1.MedusaError(utils_1.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.`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async normalizeUpdateInput_(idOrSelector, data, sharedContext = {}) {
|
|
221
|
+
let normalizedInput = [];
|
|
222
|
+
if ((0, utils_1.isString)(idOrSelector)) {
|
|
223
|
+
normalizedInput = [{ id: idOrSelector, ...data }];
|
|
224
|
+
}
|
|
225
|
+
if ((0, utils_1.isObject)(idOrSelector)) {
|
|
226
|
+
const apiKeys = await this.apiKeyService_.list(idOrSelector, {}, sharedContext);
|
|
227
|
+
normalizedInput = apiKeys.map((apiKey) => ({
|
|
228
|
+
id: apiKey.id,
|
|
229
|
+
...data,
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
return normalizedInput;
|
|
233
|
+
}
|
|
234
|
+
async validateRevokeApiKeys_(data, sharedContext = {}) {
|
|
235
|
+
if (!data.length) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (data.some((k) => !k.id)) {
|
|
239
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `You must provide an api key id field when revoking a key.`);
|
|
240
|
+
}
|
|
241
|
+
if (data.some((k) => !k.revoked_by)) {
|
|
242
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `You must provide a revoked_by field when revoking a key.`);
|
|
243
|
+
}
|
|
244
|
+
const revokedApiKeys = await this.apiKeyService_.list({
|
|
245
|
+
id: data.map((k) => k.id),
|
|
246
|
+
type: utils_1.ApiKeyType.SECRET,
|
|
247
|
+
revoked_at: { $ne: null },
|
|
248
|
+
}, {}, sharedContext);
|
|
249
|
+
if (revokedApiKeys.length) {
|
|
250
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `There are ${revokedApiKeys.length} secret keys that are already revoked.`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// These are public keys, so there is no point hashing them.
|
|
254
|
+
static generatePublishableKey() {
|
|
255
|
+
const token = "pk_" + crypto_1.default.randomBytes(32).toString("hex");
|
|
256
|
+
return {
|
|
257
|
+
rawToken: token,
|
|
258
|
+
hashedToken: token,
|
|
259
|
+
salt: "",
|
|
260
|
+
redacted: redactKey(token),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
static async generateSecretKey() {
|
|
264
|
+
const token = "sk_" + crypto_1.default.randomBytes(32).toString("hex");
|
|
265
|
+
const salt = crypto_1.default.randomBytes(16).toString("hex");
|
|
266
|
+
const hashed = await this.calculateHash(token, salt);
|
|
267
|
+
return {
|
|
268
|
+
rawToken: token,
|
|
269
|
+
hashedToken: hashed,
|
|
270
|
+
salt,
|
|
271
|
+
redacted: redactKey(token),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
static async calculateHash(token, salt) {
|
|
275
|
+
return (await scrypt(token, salt, 64)).toString("hex");
|
|
55
276
|
}
|
|
56
277
|
}
|
|
57
278
|
exports.default = ApiKeyModuleService;
|
|
@@ -66,7 +287,7 @@ __decorate([
|
|
|
66
287
|
(0, utils_1.InjectTransactionManager)("baseRepository_"),
|
|
67
288
|
__param(1, (0, utils_1.MedusaContext)()),
|
|
68
289
|
__metadata("design:type", Function),
|
|
69
|
-
__metadata("design:paramtypes", [
|
|
290
|
+
__metadata("design:paramtypes", [Array, Object]),
|
|
70
291
|
__metadata("design:returntype", Promise)
|
|
71
292
|
], ApiKeyModuleService.prototype, "create_", null);
|
|
72
293
|
__decorate([
|
|
@@ -75,25 +296,75 @@ __decorate([
|
|
|
75
296
|
__metadata("design:type", Function),
|
|
76
297
|
__metadata("design:paramtypes", [Object, Object]),
|
|
77
298
|
__metadata("design:returntype", Promise)
|
|
299
|
+
], ApiKeyModuleService.prototype, "upsert", null);
|
|
300
|
+
__decorate([
|
|
301
|
+
(0, utils_1.InjectManager)("baseRepository_"),
|
|
302
|
+
__param(2, (0, utils_1.MedusaContext)()),
|
|
303
|
+
__metadata("design:type", Function),
|
|
304
|
+
__metadata("design:paramtypes", [Object, Object, Object]),
|
|
305
|
+
__metadata("design:returntype", Promise)
|
|
78
306
|
], ApiKeyModuleService.prototype, "update", null);
|
|
79
307
|
__decorate([
|
|
80
308
|
(0, utils_1.InjectTransactionManager)("baseRepository_"),
|
|
81
309
|
__param(1, (0, utils_1.MedusaContext)()),
|
|
82
310
|
__metadata("design:type", Function),
|
|
83
|
-
__metadata("design:paramtypes", [
|
|
311
|
+
__metadata("design:paramtypes", [Array, Object]),
|
|
84
312
|
__metadata("design:returntype", Promise)
|
|
85
313
|
], ApiKeyModuleService.prototype, "update_", null);
|
|
314
|
+
__decorate([
|
|
315
|
+
(0, utils_1.InjectManager)("baseRepository_"),
|
|
316
|
+
__metadata("design:type", Function),
|
|
317
|
+
__metadata("design:paramtypes", [String, Object, Object]),
|
|
318
|
+
__metadata("design:returntype", Promise)
|
|
319
|
+
], ApiKeyModuleService.prototype, "retrieve", null);
|
|
320
|
+
__decorate([
|
|
321
|
+
(0, utils_1.InjectManager)("baseRepository_"),
|
|
322
|
+
__metadata("design:type", Function),
|
|
323
|
+
__metadata("design:paramtypes", [Object, Object, Object]),
|
|
324
|
+
__metadata("design:returntype", Promise)
|
|
325
|
+
], ApiKeyModuleService.prototype, "list", null);
|
|
326
|
+
__decorate([
|
|
327
|
+
(0, utils_1.InjectManager)("baseRepository_"),
|
|
328
|
+
__metadata("design:type", Function),
|
|
329
|
+
__metadata("design:paramtypes", [Object, Object, Object]),
|
|
330
|
+
__metadata("design:returntype", Promise)
|
|
331
|
+
], ApiKeyModuleService.prototype, "listAndCount", null);
|
|
332
|
+
__decorate([
|
|
333
|
+
(0, utils_1.InjectManager)("baseRepository_"),
|
|
334
|
+
__param(2, (0, utils_1.MedusaContext)()),
|
|
335
|
+
__metadata("design:type", Function),
|
|
336
|
+
__metadata("design:paramtypes", [Object, Object, Object]),
|
|
337
|
+
__metadata("design:returntype", Promise)
|
|
338
|
+
], ApiKeyModuleService.prototype, "revoke", null);
|
|
86
339
|
__decorate([
|
|
87
340
|
(0, utils_1.InjectTransactionManager)("baseRepository_"),
|
|
88
341
|
__param(1, (0, utils_1.MedusaContext)()),
|
|
89
342
|
__metadata("design:type", Function),
|
|
343
|
+
__metadata("design:paramtypes", [Array, Object]),
|
|
344
|
+
__metadata("design:returntype", Promise)
|
|
345
|
+
], ApiKeyModuleService.prototype, "revoke_", null);
|
|
346
|
+
__decorate([
|
|
347
|
+
(0, utils_1.InjectManager)("baseRepository_"),
|
|
348
|
+
__param(1, (0, utils_1.MedusaContext)()),
|
|
349
|
+
__metadata("design:type", Function),
|
|
90
350
|
__metadata("design:paramtypes", [String, Object]),
|
|
91
351
|
__metadata("design:returntype", Promise)
|
|
92
|
-
], ApiKeyModuleService.prototype, "
|
|
352
|
+
], ApiKeyModuleService.prototype, "authenticate", null);
|
|
93
353
|
__decorate([
|
|
94
354
|
(0, utils_1.InjectTransactionManager)("baseRepository_"),
|
|
95
355
|
__param(1, (0, utils_1.MedusaContext)()),
|
|
96
356
|
__metadata("design:type", Function),
|
|
97
357
|
__metadata("design:paramtypes", [String, Object]),
|
|
98
358
|
__metadata("design:returntype", Promise)
|
|
99
|
-
], ApiKeyModuleService.prototype, "
|
|
359
|
+
], ApiKeyModuleService.prototype, "authenticate_", null);
|
|
360
|
+
// We are mutating the object here as what microORM relies on non-enumerable fields for serialization, among other things.
|
|
361
|
+
const omitToken = (
|
|
362
|
+
// We have to make salt optional before deleting it (and we do want it required in the DB)
|
|
363
|
+
key) => {
|
|
364
|
+
key.token = key.type === utils_1.ApiKeyType.SECRET ? "" : key.token;
|
|
365
|
+
delete key.salt;
|
|
366
|
+
return key;
|
|
367
|
+
};
|
|
368
|
+
const redactKey = (key) => {
|
|
369
|
+
return [key.slice(0, 6), key.slice(-3)].join("***");
|
|
370
|
+
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
|
+
import { ApiKeyType, RevokeApiKeyDTO, UpdateApiKeyDTO } from "@medusajs/types";
|
|
1
2
|
import { IEventBusModuleService, Logger } from "@medusajs/types";
|
|
2
3
|
export type InitializeModuleInjectableDependencies = {
|
|
3
4
|
logger?: Logger;
|
|
4
5
|
eventBusService?: IEventBusModuleService;
|
|
5
6
|
};
|
|
7
|
+
export type CreateApiKeyDTO = {
|
|
8
|
+
token: string;
|
|
9
|
+
salt: string;
|
|
10
|
+
redacted: string;
|
|
11
|
+
title: string;
|
|
12
|
+
type: ApiKeyType;
|
|
13
|
+
created_by: string;
|
|
14
|
+
};
|
|
15
|
+
export type TokenDTO = {
|
|
16
|
+
rawToken: string;
|
|
17
|
+
hashedToken: string;
|
|
18
|
+
salt: string;
|
|
19
|
+
redacted: string;
|
|
20
|
+
};
|
|
21
|
+
export type UpdateApiKeyInput = UpdateApiKeyDTO & {
|
|
22
|
+
id: string;
|
|
23
|
+
};
|
|
24
|
+
export type RevokeApiKeyInput = RevokeApiKeyDTO & {
|
|
25
|
+
id: string;
|
|
26
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medusajs/api-key",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1-next-20240319121007",
|
|
4
4
|
"description": "Medusa API Key module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"prepublishOnly": "cross-env NODE_ENV=production tsc --build && tsc-alias -p tsconfig.json",
|
|
30
30
|
"build": "rimraf dist && tsc --build && tsc-alias -p tsconfig.json",
|
|
31
31
|
"test": "jest --runInBand --bail --forceExit -- src/**/__tests__/**/*.ts",
|
|
32
|
-
"test:integration": "jest --
|
|
32
|
+
"test:integration": "jest --forceExit -- integration-tests/**/__tests__/**/*.ts",
|
|
33
33
|
"migration:generate": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:generate",
|
|
34
34
|
"migration:initial": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create --initial",
|
|
35
35
|
"migration:create": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@mikro-orm/cli": "5.9.7",
|
|
41
41
|
"cross-env": "^5.2.1",
|
|
42
42
|
"jest": "^29.6.3",
|
|
43
|
-
"medusa-test-utils": "
|
|
43
|
+
"medusa-test-utils": "1.1.42-next-20240319121007",
|
|
44
44
|
"rimraf": "^3.0.2",
|
|
45
45
|
"ts-jest": "^29.1.1",
|
|
46
46
|
"ts-node": "^10.9.1",
|
|
@@ -48,14 +48,14 @@
|
|
|
48
48
|
"typescript": "^5.1.6"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@medusajs/modules-sdk": "
|
|
52
|
-
"@medusajs/types": "
|
|
53
|
-
"@medusajs/utils": "
|
|
51
|
+
"@medusajs/modules-sdk": "1.12.9-next-20240319121007",
|
|
52
|
+
"@medusajs/types": "1.11.14-next-20240319121007",
|
|
53
|
+
"@medusajs/utils": "1.11.7-next-20240319121007",
|
|
54
54
|
"@mikro-orm/core": "5.9.7",
|
|
55
55
|
"@mikro-orm/migrations": "5.9.7",
|
|
56
56
|
"@mikro-orm/postgresql": "5.9.7",
|
|
57
57
|
"awilix": "^8.0.0",
|
|
58
|
-
"dotenv": "^16.
|
|
58
|
+
"dotenv": "^16.4.5",
|
|
59
59
|
"knex": "2.4.2"
|
|
60
60
|
}
|
|
61
61
|
}
|