refactor: migrate api key module to DML (#10450)

Fixes: FRMW-2827
This commit is contained in:
Harminder Virk
2024-12-05 16:37:54 +00:00
committed by GitHub
parent 559fc6587a
commit 70d77ea22f
5 changed files with 115 additions and 117 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/api-key": patch
---
refactor: migrate api key module to DML
@@ -1,5 +1,7 @@
{ {
"namespaces": ["public"], "namespaces": [
"public"
],
"name": "public", "name": "public",
"tables": [ "tables": [
{ {
@@ -56,7 +58,11 @@
"autoincrement": false, "autoincrement": false,
"primary": false, "primary": false,
"nullable": false, "nullable": false,
"mappedType": "text" "enumItems": [
"publishable",
"secret"
],
"mappedType": "enum"
}, },
"last_used_at": { "last_used_at": {
"name": "last_used_at", "name": "last_used_at",
@@ -77,6 +83,25 @@
"nullable": false, "nullable": false,
"mappedType": "text" "mappedType": "text"
}, },
"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"
},
"created_at": { "created_at": {
"name": "created_at", "name": "created_at",
"type": "timestamptz", "type": "timestamptz",
@@ -99,17 +124,8 @@
"default": "now()", "default": "now()",
"mappedType": "datetime" "mappedType": "datetime"
}, },
"revoked_by": { "deleted_at": {
"name": "revoked_by", "name": "deleted_at",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"revoked_at": {
"name": "revoked_at",
"type": "timestamptz", "type": "timestamptz",
"unsigned": false, "unsigned": false,
"autoincrement": false, "autoincrement": false,
@@ -123,24 +139,34 @@
"schema": "public", "schema": "public",
"indexes": [ "indexes": [
{ {
"keyName": "IDX_api_key_token_unique", "keyName": "IDX_api_key_deleted_at",
"columnNames": ["token"], "columnNames": [],
"composite": false, "composite": false,
"primary": false, "primary": false,
"unique": false, "unique": false,
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_api_key_token_unique\" ON \"api_key\" (token)" "expression": "CREATE INDEX IF NOT EXISTS \"IDX_api_key_deleted_at\" ON \"api_key\" (deleted_at) WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_api_key_token_unique",
"columnNames": [],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_api_key_token_unique\" ON \"api_key\" (token) WHERE deleted_at IS NULL"
}, },
{ {
"keyName": "IDX_api_key_type", "keyName": "IDX_api_key_type",
"columnNames": ["type"], "columnNames": [],
"composite": false, "composite": false,
"primary": false, "primary": false,
"unique": false, "unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_api_key_type\" ON \"api_key\" (type)" "expression": "CREATE INDEX IF NOT EXISTS \"IDX_api_key_type\" ON \"api_key\" (type) WHERE deleted_at IS NULL"
}, },
{ {
"keyName": "api_key_pkey", "keyName": "api_key_pkey",
"columnNames": ["id"], "columnNames": [
"id"
],
"composite": false, "composite": false,
"primary": true, "primary": true,
"unique": true "unique": true
@@ -0,0 +1,32 @@
import { Migration } from "@mikro-orm/migrations"
export class Migration20241205122700 extends Migration {
async up(): Promise<void> {
this.addSql(
'alter table if exists "api_key" add column if not exists "deleted_at" timestamptz null;'
)
this.addSql(
'alter table if exists "api_key" alter column "type" type text using ("type"::text);'
)
this.addSql(
'alter table if exists "api_key" add constraint "api_key_type_check" check ("type" in (\'publishable\', \'secret\'));'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_api_key_deleted_at" ON "api_key" (deleted_at) WHERE deleted_at IS NULL;'
)
}
async down(): Promise<void> {
this.addSql(
'alter table if exists "api_key" drop constraint if exists "api_key_type_check";'
)
this.addSql(
'alter table if exists "api_key" alter column "type" type text using ("type"::text);'
)
this.addSql('drop index if exists "IDX_api_key_deleted_at";')
this.addSql(
'alter table if exists "api_key" drop column if exists "deleted_at";'
)
}
}
+23 -91
View File
@@ -1,94 +1,26 @@
import { import { model } from "@medusajs/framework/utils"
Searchable,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/framework/utils"
import { const ApiKey = model
BeforeCreate, .define("ApiKey", {
Entity, id: model.id({ prefix: "apk" }).primaryKey(),
Enum, token: model.text(),
OnInit, salt: model.text(),
PrimaryKey, redacted: model.text().searchable(),
Property, title: model.text().searchable(),
} from "@mikro-orm/core" type: model.enum(["publishable", "secret"]),
last_used_at: model.dateTime().nullable(),
const TypeIndex = createPsqlIndexStatementHelper({ created_by: model.text(),
tableName: "api_key", revoked_by: model.text().nullable(),
columns: "type", revoked_at: model.dateTime().nullable(),
})
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 .indexes([
{
on: ["token"],
unique: true,
},
{
on: ["type"],
},
])
@Property({ columnType: "text" }) export default ApiKey
created_by: string
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_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")
}
}
@@ -5,6 +5,7 @@ import {
FilterableApiKeyProps, FilterableApiKeyProps,
FindConfig, FindConfig,
IApiKeyModuleService, IApiKeyModuleService,
InferEntityType,
InternalModuleDeclaration, InternalModuleDeclaration,
ModuleJoinerConfig, ModuleJoinerConfig,
ModulesSdkTypes, ModulesSdkTypes,
@@ -46,7 +47,9 @@ export class ApiKeyModuleService
implements IApiKeyModuleService implements IApiKeyModuleService
{ {
protected baseRepository_: DAL.RepositoryService protected baseRepository_: DAL.RepositoryService
protected readonly apiKeyService_: ModulesSdkTypes.IMedusaInternalService<ApiKey> protected readonly apiKeyService_: ModulesSdkTypes.IMedusaInternalService<
InferEntityType<typeof ApiKey>
>
constructor( constructor(
{ baseRepository, apiKeyService }: InjectedDependencies, { baseRepository, apiKeyService }: InjectedDependencies,
@@ -138,7 +141,7 @@ export class ApiKeyModuleService
protected async createApiKeys_( protected async createApiKeys_(
data: ApiKeyTypes.CreateApiKeyDTO[], data: ApiKeyTypes.CreateApiKeyDTO[],
@MedusaContext() sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<[ApiKey[], TokenDTO[]]> { ): Promise<[InferEntityType<typeof ApiKey>[], TokenDTO[]]> {
await this.validateCreateApiKeys_(data, sharedContext) await this.validateCreateApiKeys_(data, sharedContext)
const normalizedInput: CreateApiKeyDTO[] = [] const normalizedInput: CreateApiKeyDTO[] = []
@@ -276,7 +279,7 @@ export class ApiKeyModuleService
protected async updateApiKeys_( protected async updateApiKeys_(
normalizedInput: UpdateApiKeyInput[], normalizedInput: UpdateApiKeyInput[],
@MedusaContext() sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<ApiKey[]> { ): Promise<InferEntityType<typeof ApiKey>[]> {
const updateRequest = normalizedInput.map((k) => ({ const updateRequest = normalizedInput.map((k) => ({
id: k.id, id: k.id,
title: k.title, title: k.title,
@@ -387,7 +390,7 @@ export class ApiKeyModuleService
async revoke_( async revoke_(
normalizedInput: RevokeApiKeyInput[], normalizedInput: RevokeApiKeyInput[],
@MedusaContext() sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<ApiKey[]> { ): Promise<InferEntityType<typeof ApiKey>[]> {
await this.validateRevokeApiKeys_(normalizedInput) await this.validateRevokeApiKeys_(normalizedInput)
const updateRequest = normalizedInput.map((k) => { const updateRequest = normalizedInput.map((k) => {
@@ -433,7 +436,7 @@ export class ApiKeyModuleService
protected async authenticate_( protected async authenticate_(
token: string, token: string,
@MedusaContext() sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<ApiKey | false> { ): Promise<InferEntityType<typeof ApiKey> | false> {
// Since we only allow up to 2 active tokens, getitng the list and checking each token isn't an issue. // 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. // We can always filter on the redacted key if we add support for an arbitrary number of tokens.
const secretKeys = await this.apiKeyService_.list( const secretKeys = await this.apiKeyService_.list(
@@ -617,8 +620,8 @@ export class ApiKeyModuleService
// We are mutating the object here as what microORM relies on non-enumerable fields for serialization, among other things. // We are mutating the object here as what microORM relies on non-enumerable fields for serialization, among other things.
const omitToken = ( const omitToken = (
// We have to make salt optional before deleting it (and we do want it required in the DB) // We have to make salt optional before deleting it (and we do want it required in the DB)
key: Omit<ApiKey, "salt"> & { salt?: string } key: Omit<InferEntityType<typeof ApiKey>, "salt"> & { salt?: string }
): Omit<ApiKey, "salt"> => { ): Omit<InferEntityType<typeof ApiKey>, "salt"> => {
key.token = key.type === ApiKeyType.SECRET ? "" : key.token key.token = key.type === ApiKeyType.SECRET ? "" : key.token
delete key.salt delete key.salt
return key return key