Skip to content

Commit

Permalink
refactor: migrate api key module to DML (medusajs#10450)
Browse files Browse the repository at this point in the history
Fixes: FRMW-2827
  • Loading branch information
thetutlage authored Dec 5, 2024
1 parent 559fc65 commit 70d77ea
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 118 deletions.
5 changes: 5 additions & 0 deletions .changeset/cuddly-students-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/api-key": patch
---

refactor: migrate api key module to DML
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"namespaces": ["public"],
"namespaces": [
"public"
],
"name": "public",
"tables": [
{
Expand Down Expand Up @@ -56,7 +58,11 @@
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
"enumItems": [
"publishable",
"secret"
],
"mappedType": "enum"
},
"last_used_at": {
"name": "last_used_at",
Expand All @@ -77,6 +83,25 @@
"nullable": false,
"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": {
"name": "created_at",
"type": "timestamptz",
Expand All @@ -99,17 +124,8 @@
"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",
"deleted_at": {
"name": "deleted_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
Expand All @@ -122,25 +138,35 @@
"name": "api_key",
"schema": "public",
"indexes": [
{
"keyName": "IDX_api_key_deleted_at",
"columnNames": [],
"composite": false,
"primary": false,
"unique": false,
"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": ["token"],
"columnNames": [],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_api_key_token_unique\" ON \"api_key\" (token)"
"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",
"columnNames": ["type"],
"columnNames": [],
"composite": false,
"primary": 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",
"columnNames": ["id"],
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
Expand Down
32 changes: 32 additions & 0 deletions packages/modules/api-key/src/migrations/Migration20241205122700.ts
Original file line number Diff line number Diff line change
@@ -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";'
)
}
}
118 changes: 25 additions & 93 deletions packages/modules/api-key/src/models/api-key.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,26 @@
import {
Searchable,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/framework/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,
import { model } from "@medusajs/framework/utils"

const ApiKey = model
.define("ApiKey", {
id: model.id({ prefix: "apk" }).primaryKey(),
token: model.text(),
salt: model.text(),
redacted: model.text().searchable(),
title: model.text().searchable(),
type: model.enum(["publishable", "secret"]),
last_used_at: model.dateTime().nullable(),
created_by: model.text(),
revoked_by: model.text().nullable(),
revoked_at: model.dateTime().nullable(),
})
last_used_at: Date | null = null

@Property({ columnType: "text" })
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")
}
}
.indexes([
{
on: ["token"],
unique: true,
},
{
on: ["type"],
},
])

export default ApiKey
17 changes: 10 additions & 7 deletions packages/modules/api-key/src/services/api-key-module-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
FilterableApiKeyProps,
FindConfig,
IApiKeyModuleService,
InferEntityType,
InternalModuleDeclaration,
ModuleJoinerConfig,
ModulesSdkTypes,
Expand Down Expand Up @@ -46,7 +47,9 @@ export class ApiKeyModuleService
implements IApiKeyModuleService
{
protected baseRepository_: DAL.RepositoryService
protected readonly apiKeyService_: ModulesSdkTypes.IMedusaInternalService<ApiKey>
protected readonly apiKeyService_: ModulesSdkTypes.IMedusaInternalService<
InferEntityType<typeof ApiKey>
>

constructor(
{ baseRepository, apiKeyService }: InjectedDependencies,
Expand Down Expand Up @@ -138,7 +141,7 @@ export class ApiKeyModuleService
protected async createApiKeys_(
data: ApiKeyTypes.CreateApiKeyDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<[ApiKey[], TokenDTO[]]> {
): Promise<[InferEntityType<typeof ApiKey>[], TokenDTO[]]> {
await this.validateCreateApiKeys_(data, sharedContext)

const normalizedInput: CreateApiKeyDTO[] = []
Expand Down Expand Up @@ -276,7 +279,7 @@ export class ApiKeyModuleService
protected async updateApiKeys_(
normalizedInput: UpdateApiKeyInput[],
@MedusaContext() sharedContext: Context = {}
): Promise<ApiKey[]> {
): Promise<InferEntityType<typeof ApiKey>[]> {
const updateRequest = normalizedInput.map((k) => ({
id: k.id,
title: k.title,
Expand Down Expand Up @@ -387,7 +390,7 @@ export class ApiKeyModuleService
async revoke_(
normalizedInput: RevokeApiKeyInput[],
@MedusaContext() sharedContext: Context = {}
): Promise<ApiKey[]> {
): Promise<InferEntityType<typeof ApiKey>[]> {
await this.validateRevokeApiKeys_(normalizedInput)

const updateRequest = normalizedInput.map((k) => {
Expand Down Expand Up @@ -433,7 +436,7 @@ export class ApiKeyModuleService
protected async authenticate_(
token: string,
@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.
// We can always filter on the redacted key if we add support for an arbitrary number of tokens.
const secretKeys = await this.apiKeyService_.list(
Expand Down Expand Up @@ -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.
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: Omit<InferEntityType<typeof ApiKey>, "salt"> & { salt?: string }
): Omit<InferEntityType<typeof ApiKey>, "salt"> => {
key.token = key.type === ApiKeyType.SECRET ? "" : key.token
delete key.salt
return key
Expand Down

0 comments on commit 70d77ea

Please sign in to comment.