feat(types,module-sdk): setup authentication module skeleton (#5943)

**What**
- add authentication module skeleton
This commit is contained in:
Philip Korsholm
2023-12-21 07:56:20 +01:00
committed by GitHub
parent 3f6d79961d
commit 6d1e3cc028
44 changed files with 720 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
---
"@medusajs/modules-sdk": patch
"@medusajs/types": patch
---
feat(modules-sdk, types): add initial authentication module configuration

6
packages/authentication/.gitignore vendored Normal file
View File

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

View File

View File

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

View File

@@ -0,0 +1,5 @@
describe("Noop test", () => {
it("noop check", async () => {
expect(true).toBe(true)
})
})

View File

@@ -0,0 +1,6 @@
if (typeof process.env.DB_TEMP_NAME === "undefined") {
const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
process.env.DB_TEMP_NAME = `medusa-authentication-integration-${tempName}`
}
process.env.MEDUSA_AUTHENTICATION_DB_SCHEMA = "public"

View File

@@ -0,0 +1,3 @@
import { JestUtils } from "medusa-test-utils"
JestUtils.afterAllHookDropDatabase()

View File

@@ -0,0 +1,6 @@
import { ModuleServiceInitializeOptions } from "@medusajs/types"
export const databaseOptions: ModuleServiceInitializeOptions["database"] = {
schema: "public",
clientUrl: "medusa-authentication-test",
}

View File

@@ -0,0 +1,18 @@
import { TestDatabaseUtils } from "medusa-test-utils"
import * as AuthenticationModels from "@models"
const pathToMigrations = "../../src/migrations"
const mikroOrmEntities = AuthenticationModels as unknown as any[]
export const MikroOrmWrapper = TestDatabaseUtils.getMikroOrmWrapper(
mikroOrmEntities,
pathToMigrations
)
export const MikroOrmConfig = TestDatabaseUtils.getMikroOrmConfig(
mikroOrmEntities,
pathToMigrations
)
export const DB_URL = TestDatabaseUtils.getDatabaseURL()

View File

@@ -0,0 +1,2 @@
export * from "./config"
export * from "./database"

View File

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

View File

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

View File

@@ -0,0 +1,63 @@
{
"name": "@medusajs/authentication",
"version": "0.0.1",
"description": "Medusa Authentication module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"engines": {
"node": ">=16"
},
"bin": {
"medusa-authentication-migrations-down": "dist/scripts/bin/run-migration-down.js",
"medusa-authentication-migrations-up": "dist/scripts/bin/run-migration-up.js",
"medusa-authentication-seed": "dist/scripts/bin/run-seed.js"
},
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
"directory": "packages/authentication"
},
"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 --runInBand --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.7.12",
"cross-env": "^5.2.1",
"jest": "^29.6.3",
"medusa-test-utils": "^1.1.40",
"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.5",
"@medusajs/types": "^1.11.9",
"@medusajs/utils": "^1.11.2",
"@mikro-orm/core": "5.7.12",
"@mikro-orm/migrations": "5.7.12",
"@mikro-orm/postgresql": "5.7.12",
"awilix": "^8.0.0",
"dotenv": "^16.1.4",
"knex": "2.4.2"
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,38 @@
import * as defaultRepositories from "@repositories"
import { LoaderOptions } from "@medusajs/modules-sdk"
import { ModulesSdkTypes } from "@medusajs/types"
import { loadCustomRepositories } from "@medusajs/utils"
import { asClass } from "awilix"
export default async ({
container,
options,
}: LoaderOptions<
| ModulesSdkTypes.ModuleServiceInitializeOptions
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
>): Promise<void> => {
const customRepositories = (
options as ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
)?.repositories
container.register({
// authenticationService: asClass(defaultServices.AuthenticationService).singleton(),
})
if (customRepositories) {
loadCustomRepositories({
defaultRepositories,
customRepositories,
container,
})
} else {
loadDefaultRepositories({ container })
}
}
function loadDefaultRepositories({ container }) {
container.register({
baseRepository: asClass(defaultRepositories.BaseRepository).singleton(),
})
}

View File

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

View File

@@ -0,0 +1,36 @@
{
"namespaces": [
"public"
],
"name": "public",
"tables": [
{
"columns": {
"id": {
"name": "id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
}
},
"name": "auth_user",
"schema": "public",
"indexes": [
{
"keyName": "auth_user_pkey",
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
}
]
}

View File

@@ -0,0 +1,13 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20231220132440 extends Migration {
async up(): Promise<void> {
this.addSql('create table "auth_user" ("id" text not null, constraint "auth_user_pkey" primary key ("id"));');
}
async down(): Promise<void> {
this.addSql('drop table if exists "auth_user" cascade;');
}
}

View File

@@ -0,0 +1,18 @@
import { generateEntityId } from "@medusajs/utils"
import { BeforeCreate, Entity, OnInit, PrimaryKey } from "@mikro-orm/core"
@Entity()
export default class AuthUser {
@PrimaryKey({ columnType: "text" })
id!: string
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "authusr")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "authusr")
}
}

View File

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

View File

@@ -0,0 +1,12 @@
import { ModuleExports } from "@medusajs/types"
import { AuthenticationModuleService } from "@services"
import loadConnection from "./loaders/connection"
import loadContainer from "./loaders/container"
const service = AuthenticationModuleService
const loaders = [loadContainer, loadConnection] as any
export const moduleDefinition: ModuleExports = {
service,
loaders,
}

View File

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

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env node
export default (async () => {
const { revertMigration } = await import("../migration-down")
const { config } = await import("dotenv")
config()
await revertMigration()
})()

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env node
export default (async () => {
const { runMigrations } = await import("../migration-up")
const { config } = await import("dotenv")
config()
await runMigrations()
})()

View File

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

View File

@@ -0,0 +1,2 @@
export * from "./migration-up"
export * from "./migration-down"

View File

@@ -0,0 +1,50 @@
import { Modules } from "@medusajs/modules-sdk";
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types";
import { DALUtils, ModulesSdkUtils } from "@medusajs/utils";
import { EntitySchema } from "@mikro-orm/core";
import * as AuthenticationModels from "@models";
/**
* This script is only valid for mikro orm managers. If a user provide a custom manager
* he is in charge of reverting the migrations.
* @param options
* @param logger
* @param moduleDeclaration
*/
export async function revertMigration({
options,
logger,
}: Pick<
LoaderOptions<ModulesSdkTypes.ModuleServiceInitializeOptions>,
"options" | "logger"
> = {}) {
logger ??= console as unknown as Logger
const dbData = ModulesSdkUtils.loadDatabaseConfig(
Modules.AUTHENTICATION,
options
)!
const entities = Object.values(
AuthenticationModels
) as unknown as EntitySchema[]
const pathToMigrations = __dirname + "/../migrations"
const orm = await DALUtils.mikroOrmCreateConnection(
dbData,
entities,
pathToMigrations
)
try {
const migrator = orm.getMigrator()
await migrator.down()
logger?.info("Authentication module migration executed")
} catch (error) {
logger?.error(
`Authentication module migration failed to run - Error: ${error}`
)
}
await orm.close()
}

View File

@@ -0,0 +1,62 @@
import { Modules } from "@medusajs/modules-sdk";
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types";
import { DALUtils, ModulesSdkUtils } from "@medusajs/utils";
import { EntitySchema } from "@mikro-orm/core";
import * as AuthenticationModels from "@models";
/**
* This script is only valid for mikro orm managers. If a user provide a custom manager
* he is in charge of running the migrations.
* @param options
* @param logger
* @param moduleDeclaration
*/
export async function runMigrations({
options,
logger,
}: Pick<
LoaderOptions<ModulesSdkTypes.ModuleServiceInitializeOptions>,
"options" | "logger"
> = {}) {
logger ??= console as unknown as Logger
const dbData = ModulesSdkUtils.loadDatabaseConfig(
Modules.AUTHENTICATION,
options
)!
const entities = Object.values(
AuthenticationModels
) as unknown as EntitySchema[]
const pathToMigrations = __dirname + "/../migrations"
const orm = await DALUtils.mikroOrmCreateConnection(
dbData,
entities,
pathToMigrations
)
try {
const migrator = orm.getMigrator()
const pendingMigrations = await migrator.getPendingMigrations()
logger.info(
`Running pending migrations: ${JSON.stringify(
pendingMigrations,
null,
2
)}`
)
await migrator.up({
migrations: pendingMigrations.map((m) => m.name),
})
logger.info("Authentication module migration executed")
} catch (error) {
logger.error(
`Authentication module migration failed to run - Error: ${error}`
)
}
await orm.close()
}

View File

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

View File

@@ -0,0 +1,5 @@
describe("Noop test", () => {
it("noop check", async () => {
expect(true).toBe(true)
})
})

View File

@@ -0,0 +1,32 @@
import {
AuthenticationTypes,
DAL,
InternalModuleDeclaration,
ModuleJoinerConfig,
} from "@medusajs/types"
import { AuthUser } from "@models"
import { joinerConfig } from "../joiner-config"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
}
export default class AuthenticationModuleService<
TAuthUser extends AuthUser = AuthUser
> implements AuthenticationTypes.IAuthenticationModuleService
{
protected baseRepository_: DAL.RepositoryService
constructor(
{ baseRepository }: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
this.baseRepository_ = baseRepository
}
__joinerConfig(): ModuleJoinerConfig {
return joinerConfig
}
}

View File

@@ -0,0 +1 @@
export { default as AuthenticationModuleService } from "./authentication-module"

View File

@@ -0,0 +1,5 @@
import { Logger } from "@medusajs/types"
export type InitializeModuleInjectableDependencies = {
logger?: Logger
}

View File

@@ -0,0 +1,36 @@
{
"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"]
}
},
"include": ["src"],
"exclude": [
"dist",
"./src/**/__tests__",
"./src/**/__mocks__",
"./src/**/__fixtures__",
"node_modules"
]
}

View File

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

View File

@@ -14,6 +14,7 @@ export enum Modules {
PRODUCT = "productService",
PRICING = "pricingService",
PROMOTION = "promotion",
AUTHENTICATION = "authentication",
}
export enum ModuleRegistrationName {
@@ -24,6 +25,7 @@ export enum ModuleRegistrationName {
PRODUCT = "productModuleService",
PRICING = "pricingModuleService",
PROMOTION = "promotionModuleService",
AUTHENTICATION = "authenticationModuleService",
}
export const MODULE_PACKAGE_NAMES = {
@@ -34,6 +36,7 @@ export const MODULE_PACKAGE_NAMES = {
[Modules.CACHE]: "@medusajs/cache-inmemory",
[Modules.PRICING]: "@medusajs/pricing",
[Modules.PROMOTION]: "@medusajs/promotion",
[Modules.AUTHENTICATION]: "@medusajs/authentication",
}
export const ModulesDefinition: { [key: string | Modules]: ModuleDefinition } =
@@ -137,6 +140,20 @@ export const ModulesDefinition: { [key: string | Modules]: ModuleDefinition } =
resources: MODULE_RESOURCE_TYPE.SHARED,
},
},
[Modules.AUTHENTICATION]: {
key: Modules.AUTHENTICATION,
registrationName: ModuleRegistrationName.AUTHENTICATION,
defaultPackage: false,
label: upperCaseFirst(ModuleRegistrationName.AUTHENTICATION),
isRequired: false,
canOverride: true,
isQueryable: true,
dependencies: ["logger"],
defaultModuleDeclaration: {
scope: MODULE_SCOPE.INTERNAL,
resources: MODULE_RESOURCE_TYPE.SHARED,
},
},
}
export const MODULE_DEFINITIONS: ModuleDefinition[] =

View File

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

View File

@@ -0,0 +1,3 @@
import { IModuleService } from "../modules-sdk"
export interface IAuthenticationModuleService extends IModuleService {}

View File

@@ -1,3 +1,4 @@
export * as AuthenticationTypes from "./authentication"
export * as CacheTypes from "./cache"
export * as CommonTypes from "./common"
export * as CustomerTypes from "./customer"

View File

@@ -1,4 +1,5 @@
export * from "./address"
export * from "./authentication"
export * from "./bundles"
export * from "./cache"
export * from "./cart"

View File

@@ -7405,6 +7405,35 @@ __metadata:
languageName: unknown
linkType: soft
"@medusajs/authentication@workspace:packages/authentication":
version: 0.0.0-use.local
resolution: "@medusajs/authentication@workspace:packages/authentication"
dependencies:
"@medusajs/modules-sdk": ^1.12.5
"@medusajs/types": ^1.11.9
"@medusajs/utils": ^1.11.2
"@mikro-orm/cli": 5.7.12
"@mikro-orm/core": 5.7.12
"@mikro-orm/migrations": 5.7.12
"@mikro-orm/postgresql": 5.7.12
awilix: ^8.0.0
cross-env: ^5.2.1
dotenv: ^16.1.4
jest: ^29.6.3
knex: 2.4.2
medusa-test-utils: ^1.1.40
rimraf: ^3.0.2
ts-jest: ^29.1.1
ts-node: ^10.9.1
tsc-alias: ^1.8.6
typescript: ^5.1.6
bin:
medusa-authentication-migrations-down: dist/scripts/bin/run-migration-down.js
medusa-authentication-migrations-up: dist/scripts/bin/run-migration-up.js
medusa-authentication-seed: dist/scripts/bin/run-seed.js
languageName: unknown
linkType: soft
"@medusajs/cache-inmemory@workspace:*, @medusajs/cache-inmemory@workspace:packages/cache-inmemory":
version: 0.0.0-use.local
resolution: "@medusajs/cache-inmemory@workspace:packages/cache-inmemory"