Feat(auth): Rename authentication to auth (#6229)

**What**
- rename `authenticationModule` -> `authModule`
This commit is contained in:
Philip Korsholm
2024-01-29 10:19:30 +00:00
committed by GitHub
parent a41aad4bea
commit 512b041929
70 changed files with 245 additions and 147 deletions
+11
View File
@@ -0,0 +1,11 @@
import {
moduleDefinition,
revertMigration,
runMigrations,
} from "./module-definition"
export default moduleDefinition
export { revertMigration, runMigrations }
export * from "./initialize"
export * from "./loaders"
+31
View File
@@ -0,0 +1,31 @@
import {
ExternalModuleDeclaration,
InternalModuleDeclaration,
MODULE_PACKAGE_NAMES,
MedusaModule,
Modules,
} from "@medusajs/modules-sdk"
import { IAuthModuleService, ModulesSdkTypes } from "@medusajs/types"
import { InitializeModuleInjectableDependencies } from "../types"
import { moduleDefinition } from "../module-definition"
export const initialize = async (
options?:
| ModulesSdkTypes.ModuleBootstrapDeclaration
| ModulesSdkTypes.ModuleServiceInitializeOptions
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions,
injectedDependencies?: InitializeModuleInjectableDependencies
): Promise<IAuthModuleService> => {
const loaded = await MedusaModule.bootstrap<IAuthModuleService>({
moduleKey: Modules.AUTH,
defaultPath: MODULE_PACKAGE_NAMES[Modules.AUTH],
declaration: options as
| InternalModuleDeclaration
| ExternalModuleDeclaration, // TODO: Add provider configuration
injectedDependencies,
moduleExports: moduleDefinition,
})
return loaded[Modules.AUTH]
}
+31
View File
@@ -0,0 +1,31 @@
import { AuthUser } from "@models"
import { MapToConfig } from "@medusajs/utils"
import { ModuleJoinerConfig } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
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.AUTH,
primaryKeys: ["id"],
linkableKeys: LinkableKeys,
alias: {
name: ["auth_user", "auth_users"],
args: {
entity: AuthUser.name,
},
},
}
+38
View File
@@ -0,0 +1,38 @@
import * as AuthModels from "../models"
import {
InternalModuleDeclaration,
LoaderOptions,
Modules,
} from "@medusajs/modules-sdk"
import { EntitySchema } from "@mikro-orm/core"
import { ModulesSdkTypes } from "@medusajs/types"
import { ModulesSdkUtils } from "@medusajs/utils"
export default async (
{
options,
container,
logger,
}: LoaderOptions<
| ModulesSdkTypes.ModuleServiceInitializeOptions
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
>,
moduleDeclaration?: InternalModuleDeclaration
): Promise<void> => {
const entities = Object.values(
AuthModels
) as unknown as EntitySchema[]
const pathToMigrations = __dirname + "/../migrations"
await ModulesSdkUtils.mikroOrmConnectionLoader({
moduleName: Modules.AUTH,
entities,
container,
options,
moduleDeclaration,
logger,
pathToMigrations,
})
}
+10
View File
@@ -0,0 +1,10 @@
import { ModulesSdkUtils } from "@medusajs/utils"
import * as ModuleModels from "@models"
import * as ModuleRepositories from "@repositories"
import * as ModuleServices from "@services"
export default ModulesSdkUtils.moduleContainerLoaderFactory({
moduleModels: ModuleModels,
moduleRepositories: ModuleRepositories,
moduleServices: ModuleServices,
})
+3
View File
@@ -0,0 +1,3 @@
export * from "./connection"
export * from "./container"
export * from "./providers"
+71
View File
@@ -0,0 +1,71 @@
import * as defaultProviders from "@providers"
import {
asClass,
AwilixContainer,
ClassOrFunctionReturning,
Constructor,
Resolver,
} from "awilix"
import {
AuthModuleProviderConfig,
AuthProviderScope,
LoaderOptions,
ModulesSdkTypes,
} from "@medusajs/types"
type AuthModuleProviders = {
providers: AuthModuleProviderConfig[]
}
export default async ({
container,
options,
}: LoaderOptions<
(
| ModulesSdkTypes.ModuleServiceInitializeOptions
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
) &
AuthModuleProviders
>): Promise<void> => {
const providerMap = new Map(
options?.providers?.map((provider) => [provider.name, provider.scopes]) ??
[]
)
// if(options?.providers?.length) {
// TODO: implement plugin provider registration
// }
const providersToLoad = Object.values(defaultProviders)
for (const provider of providersToLoad) {
container.register({
[`auth_provider_${provider.PROVIDER}`]: asClass(
provider as Constructor<any>
)
.singleton()
.inject(() => ({ scopes: providerMap.get(provider.PROVIDER) ?? {} })),
})
}
container.register({
[`auth_providers`]: asArray(providersToLoad, providerMap),
})
}
function asArray(
resolvers: (ClassOrFunctionReturning<unknown> | Resolver<unknown>)[],
providerScopeMap: Map<string, Record<string, AuthProviderScope>>
): { resolve: (container: AwilixContainer) => unknown[] } {
return {
resolve: (container: AwilixContainer) =>
resolvers.map((resolver) =>
asClass(resolver as Constructor<any>)
.inject(() => ({
// @ts-ignore
scopes: providerScopeMap.get(resolver.PROVIDER) ?? {},
}))
.resolve(container)
),
}
}
@@ -0,0 +1,175 @@
{
"namespaces": [
"public"
],
"name": "public",
"tables": [
{
"columns": {
"provider": {
"name": "provider",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"name": {
"name": "name",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"domain": {
"name": "domain",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"default": "'all'",
"enumItems": [
"all",
"store",
"admin"
],
"mappedType": "enum"
},
"config": {
"name": "config",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"is_active": {
"name": "is_active",
"type": "boolean",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"default": "false",
"mappedType": "boolean"
}
},
"name": "auth_provider",
"schema": "public",
"indexes": [
{
"keyName": "auth_provider_pkey",
"columnNames": [
"provider"
],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
},
{
"columns": {
"id": {
"name": "id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"entity_id": {
"name": "entity_id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"provider_id": {
"name": "provider_id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"user_metadata": {
"name": "user_metadata",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"app_metadata": {
"name": "app_metadata",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"provider_metadata": {
"name": "provider_metadata",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
}
},
"name": "auth_user",
"schema": "public",
"indexes": [
{
"keyName": "IDX_auth_user_provider_entity_id",
"columnNames": [
"provider_id",
"entity_id"
],
"composite": true,
"primary": false,
"unique": true
},
{
"keyName": "auth_user_pkey",
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {
"auth_user_provider_id_foreign": {
"constraintName": "auth_user_provider_id_foreign",
"columnNames": [
"provider_id"
],
"localTableName": "public.auth_user",
"referencedColumnNames": [
"provider"
],
"referencedTableName": "public.auth_provider",
"deleteRule": "cascade"
}
}
}
]
}
@@ -0,0 +1,22 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20240122041959 extends Migration {
async up(): Promise<void> {
this.addSql('create table if not exists "auth_provider" ("provider" text not null, "name" text not null, "domain" text check ("domain" in (\'all\', \'store\', \'admin\')) not null default \'all\', "config" jsonb null, "is_active" boolean not null default false, constraint "auth_provider_pkey" primary key ("provider"));');
this.addSql('create table if not exists "auth_user" ("id" text not null, "entity_id" text not null, "provider_id" text null, "user_metadata" jsonb null, "app_metadata" jsonb null, "provider_metadata" jsonb null, constraint "auth_user_pkey" primary key ("id"));');
this.addSql('alter table "auth_user" add constraint "IDX_auth_user_provider_entity_id" unique ("provider_id", "entity_id");');
this.addSql('alter table "auth_user" add constraint if not exists "auth_user_provider_id_foreign" foreign key ("provider_id") references "auth_provider" ("provider") on delete cascade;');
}
async down(): Promise<void> {
this.addSql('alter table "auth_user" drop constraint if exists "auth_user_provider_id_foreign";');
this.addSql('drop table if exists "auth_provider" cascade;');
this.addSql('drop table if exists "auth_user" cascade;');
}
}
+31
View File
@@ -0,0 +1,31 @@
import {
Entity,
Enum,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import { ProviderDomain } from "../types/repositories/auth-provider"
type OptionalFields = "domain" | "is_active" | "config"
@Entity()
export default class AuthProvider {
[OptionalProps]: OptionalFields
@PrimaryKey({ columnType: "text" })
provider!: string
@Property({ columnType: "text" })
name: string
@Enum({ items: () => ProviderDomain, default: ProviderDomain.ALL })
domain: ProviderDomain = ProviderDomain.ALL
@Property({ columnType: "jsonb", nullable: true })
config: Record<string, unknown> | null = null
@Property({ columnType: "boolean", default: false })
is_active = false
}
+55
View File
@@ -0,0 +1,55 @@
import {
BeforeCreate,
Cascade,
Entity,
Index,
ManyToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
Unique,
} from "@mikro-orm/core"
import AuthProvider from "./auth-provider"
import { generateEntityId } from "@medusajs/utils"
type OptionalFields = "provider_metadata" | "app_metadata" | "user_metadata"
@Entity()
@Unique({ properties: ["provider","entity_id" ], name: "IDX_auth_user_provider_entity_id" })
export default class AuthUser {
[OptionalProps]: OptionalFields
@PrimaryKey({ columnType: "text" })
id!: string
@Property({ columnType: "text" })
entity_id: string
@ManyToOne(() => AuthProvider, {
joinColumn: "provider",
fieldName: "provider_id",
cascade: [Cascade.REMOVE],
})
provider: AuthProvider
@Property({ columnType: "jsonb", nullable: true })
user_metadata: Record<string, unknown> | null
@Property({ columnType: "jsonb", nullable: true })
app_metadata: Record<string, unknown> | null
@Property({ columnType: "jsonb", nullable: true })
provider_metadata: Record<string, unknown> | null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "authusr")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "authusr")
}
}
+2
View File
@@ -0,0 +1,2 @@
export { default as AuthUser } from "./auth-user"
export { default as AuthProvider } from "./auth-provider"
+32
View File
@@ -0,0 +1,32 @@
import * as Models from "@models"
import { AuthModuleService } from "@services"
import { ModuleExports } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
import { ModulesSdkUtils } from "@medusajs/utils"
import loadConnection from "./loaders/connection"
import loadContainer from "./loaders/container"
import loadProviders from "./loaders/providers"
const migrationScriptOptions = {
moduleName: Modules.AUTH,
models: Models,
pathToMigrations: __dirname + "/migrations",
}
export const runMigrations = ModulesSdkUtils.buildMigrationScript(
migrationScriptOptions
)
export const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
migrationScriptOptions
)
const service = AuthModuleService
const loaders = [loadContainer, loadConnection, loadProviders] as any
export const moduleDefinition: ModuleExports = {
service,
loaders,
runMigrations,
revertMigration,
}
@@ -0,0 +1,64 @@
import { AbstractAuthModuleProvider, isString } from "@medusajs/utils"
import { AuthenticationInput, AuthenticationResponse } from "@medusajs/types"
import { AuthUserService } from "@services"
import Scrypt from "scrypt-kdf"
class EmailPasswordProvider extends AbstractAuthModuleProvider {
public static PROVIDER = "emailpass"
public static DISPLAY_NAME = "Email/Password Authentication"
protected readonly authUserSerivce_: AuthUserService
constructor({ authUserService }: { authUserService: AuthUserService }) {
super(arguments[0])
this.authUserSerivce_ = authUserService
}
async authenticate(
userData: AuthenticationInput
): Promise<AuthenticationResponse> {
const { email, password } = userData.body
if (!password || !isString(password)) {
return {
success: false,
error: "Password should be a string",
}
}
if (!email || !isString(email)) {
return {
success: false,
error: "Email should be a string",
}
}
const authUser = await this.authUserSerivce_.retrieveByProviderAndEntityId(
email,
EmailPasswordProvider.PROVIDER
)
const password_hash = authUser.provider_metadata?.password
if (isString(password_hash)) {
const buf = Buffer.from(password_hash as string, "base64")
const success = await Scrypt.verify(buf, password)
if (success) {
delete authUser.provider_metadata!.password
return { success, authUser: JSON.parse(JSON.stringify(authUser)) }
}
}
return {
success: false,
error: "Invalid email or password",
}
}
}
export default EmailPasswordProvider
+226
View File
@@ -0,0 +1,226 @@
import {
AbstractAuthModuleProvider,
MedusaError,
} from "@medusajs/utils"
import {
AuthProviderScope,
AuthenticationInput,
AuthenticationResponse,
} from "@medusajs/types"
import { AuthProviderService, AuthUserService } from "@services"
import jwt, { JwtPayload } from "jsonwebtoken"
import { AuthorizationCode } from "simple-oauth2"
import url from "url"
type InjectedDependencies = {
authUserService: AuthUserService
authProviderService: AuthProviderService
}
type ProviderConfig = {
clientID: string
clientSecret: string
callbackURL: string
}
class GoogleProvider extends AbstractAuthModuleProvider {
public static PROVIDER = "google"
public static DISPLAY_NAME = "Google Authentication"
protected readonly authUserSerivce_: AuthUserService
protected readonly authProviderService_: AuthProviderService
constructor({ authUserService, authProviderService }: InjectedDependencies) {
super(arguments[0])
this.authUserSerivce_ = authUserService
this.authProviderService_ = authProviderService
}
async authenticate(
req: AuthenticationInput
): Promise<AuthenticationResponse> {
if (req.query?.error) {
return {
success: false,
error: `${req.query.error_description}, read more at: ${req.query.error_uri}`,
}
}
let config: ProviderConfig
try {
config = await this.getProviderConfig(req)
} catch (error) {
return { success: false, error: error.message }
}
return this.getRedirect(config)
}
async validateCallback(
req: AuthenticationInput
): Promise<AuthenticationResponse> {
if (req.query && req.query.error) {
return {
success: false,
error: `${req.query.error_description}, read more at: ${req.query.error_uri}`,
}
}
let config: ProviderConfig
try {
config = await this.getProviderConfig(req)
} catch (error) {
return { success: false, error: error.message }
}
const code = req.query?.code ?? req.body?.code
return await this.validateCallbackToken(code, req.scope, config)
}
// abstractable
async verify_(refreshToken: string, scope: string) {
const jwtData = (await jwt.decode(refreshToken, {
complete: true,
})) as JwtPayload
const entity_id = jwtData.payload.email
let authUser
try {
authUser = await this.authUserSerivce_.retrieveByProviderAndEntityId(
entity_id,
GoogleProvider.PROVIDER
)
} catch (error) {
if (error.type === MedusaError.Types.NOT_FOUND) {
authUser = await this.authUserSerivce_.create([
{
entity_id,
provider_id: GoogleProvider.PROVIDER,
user_metadata: jwtData!.payload,
app_metadata: { scope },
},
])
} else {
return { success: false, error: error.message }
}
}
return { success: true, authUser }
}
// abstractable
private async validateCallbackToken(
code: string,
scope: string,
{ clientID, callbackURL, clientSecret }: ProviderConfig
) {
const client = this.getAuthorizationCodeHandler({ clientID, clientSecret })
const tokenParams = {
code,
redirect_uri: callbackURL,
}
try {
const accessToken = await client.getToken(tokenParams)
return await this.verify_(accessToken.token.id_token, scope)
} catch (error) {
return { success: false, error: error.message }
}
}
private getConfigFromScope(config: AuthProviderScope): ProviderConfig {
const providerConfig: Partial<ProviderConfig> = {}
if (config.clientId) {
providerConfig.clientID = config.clientId
} else {
throw new Error("Google clientID is required")
}
if (config.clientSecret) {
providerConfig.clientSecret = config.clientSecret
} else {
throw new Error("Google clientSecret is required")
}
if (config.callbackURL) {
providerConfig.callbackURL = config.callbackUrl
} else {
throw new Error("Google callbackUrl is required")
}
return providerConfig as ProviderConfig
}
private originalURL(req: AuthenticationInput) {
const tls = req.connection.encrypted
const host = req.headers.host
const protocol = tls ? "https" : "http"
const path = req.url || ""
return protocol + "://" + host + path
}
private async getProviderConfig(
req: AuthenticationInput
): Promise<ProviderConfig> {
await this.authProviderService_.retrieve(GoogleProvider.PROVIDER)
const scopeConfig = this.scopes_[req.scope]
const config = this.getConfigFromScope(scopeConfig)
const { callbackURL } = config
const parsedCallbackUrl = !url.parse(callbackURL).protocol
? url.resolve(this.originalURL(req), callbackURL)
: callbackURL
return { ...config, callbackURL: parsedCallbackUrl }
}
// Abstractable
private getRedirect({ clientID, callbackURL, clientSecret }: ProviderConfig) {
const client = this.getAuthorizationCodeHandler({ clientID, clientSecret })
const location = client.authorizeURL({
redirect_uri: callbackURL,
scope: "email profile",
})
return { success: true, location }
}
private getAuthorizationCodeHandler({
clientID,
clientSecret,
}: {
clientID: string
clientSecret: string
}) {
const config = {
client: {
id: clientID,
secret: clientSecret,
},
auth: {
// TODO: abstract to not be google specific
authorizeHost: "https://accounts.google.com",
authorizePath: "/o/oauth2/v2/auth",
tokenHost: "https://www.googleapis.com",
tokenPath: "/oauth2/v4/token",
},
}
return new AuthorizationCode(config)
}
}
export default GoogleProvider
+2
View File
@@ -0,0 +1,2 @@
export { default as EmailPasswordProvider } from "./email-password"
export { default as GoogleProvider } from "./google"
+1
View File
@@ -0,0 +1 @@
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
+19
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-auth-seed <filePath>`
)
}
await run({ path })
})()
+65
View File
@@ -0,0 +1,65 @@
import * as AuthModels from "@models"
import { DALUtils, ModulesSdkUtils } from "@medusajs/utils"
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
import { EOL } from "os"
import { EntitySchema } from "@mikro-orm/core"
import { Modules } from "@medusajs/modules-sdk"
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.AUTH,
options
)!
const entities = Object.values(
AuthModels
) 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)
}
@@ -0,0 +1,5 @@
describe("Noop test", () => {
it("noop check", async () => {
expect(true).toBe(true)
})
})
+435
View File
@@ -0,0 +1,435 @@
import {
AuthenticationInput,
AuthenticationResponse,
AuthTypes,
Context,
DAL,
FindConfig,
InternalModuleDeclaration,
MedusaContainer,
ModuleJoinerConfig,
} from "@medusajs/types"
import { AuthProvider, AuthUser } from "@models"
import { joinerConfig } from "../joiner-config"
import { AuthProviderService, AuthUserService } from "@services"
import {
AbstractAuthModuleProvider,
InjectManager,
InjectTransactionManager,
MedusaContext,
MedusaError,
} from "@medusajs/utils"
import {
AuthProviderDTO,
AuthUserDTO,
CreateAuthProviderDTO,
CreateAuthUserDTO,
FilterableAuthProviderProps,
FilterableAuthUserProps,
UpdateAuthUserDTO,
} from "@medusajs/types"
import { ServiceTypes } from "@types"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
authUserService: AuthUserService<any>
authProviderService: AuthProviderService<any>
}
export default class AuthModuleService<
TAuthUser extends AuthUser = AuthUser,
TAuthProvider extends AuthProvider = AuthProvider
> implements AuthTypes.IAuthModuleService
{
__joinerConfig(): ModuleJoinerConfig {
return joinerConfig
}
__hooks = {
onApplicationStart: async () => await this.createProvidersOnLoad(),
}
protected __container__: MedusaContainer
protected baseRepository_: DAL.RepositoryService
protected authUserService_: AuthUserService<TAuthUser>
protected authProviderService_: AuthProviderService<TAuthProvider>
constructor(
{
authUserService,
authProviderService,
baseRepository,
}: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
this.__container__ = arguments[0]
this.baseRepository_ = baseRepository
this.authUserService_ = authUserService
this.authProviderService_ = authProviderService
}
async retrieveAuthProvider(
provider: string,
config: FindConfig<AuthProviderDTO> = {},
sharedContext: Context = {}
): Promise<AuthProviderDTO> {
const authProvider = await this.authProviderService_.retrieve(
provider,
config,
sharedContext
)
return await this.baseRepository_.serialize<AuthTypes.AuthProviderDTO>(
authProvider,
{ populate: true }
)
}
async listAuthProviders(
filters: FilterableAuthProviderProps = {},
config: FindConfig<AuthProviderDTO> = {},
sharedContext: Context = {}
): Promise<AuthProviderDTO[]> {
const authProviders = await this.authProviderService_.list(
filters,
config,
sharedContext
)
return await this.baseRepository_.serialize<
AuthTypes.AuthProviderDTO[]
>(authProviders, { populate: true })
}
@InjectManager("baseRepository_")
async listAndCountAuthProviders(
filters: FilterableAuthProviderProps = {},
config: FindConfig<AuthProviderDTO>,
@MedusaContext() sharedContext: Context = {}
): Promise<[AuthTypes.AuthProviderDTO[], number]> {
const [authProviders, count] = await this.authProviderService_.listAndCount(
filters,
config,
sharedContext
)
return [
await this.baseRepository_.serialize<
AuthTypes.AuthProviderDTO[]
>(authProviders, { populate: true }),
count,
]
}
async createAuthProvider(
data: CreateAuthProviderDTO[],
sharedContext?: Context
): Promise<AuthProviderDTO[]>
async createAuthProvider(
data: CreateAuthProviderDTO,
sharedContext?: Context
): Promise<AuthProviderDTO>
@InjectManager("baseRepository_")
async createAuthProvider(
data: CreateAuthProviderDTO | CreateAuthProviderDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<
AuthTypes.AuthProviderDTO | AuthTypes.AuthProviderDTO[]
> {
const input = Array.isArray(data) ? data : [data]
const providers = await this.createAuthProviders_(input, sharedContext)
const serializedProviders = await this.baseRepository_.serialize<
AuthTypes.AuthProviderDTO[]
>(providers, {
populate: true,
})
return Array.isArray(data) ? serializedProviders : serializedProviders[0]
}
@InjectTransactionManager("baseRepository_")
protected async createAuthProviders_(
data: any[],
@MedusaContext() sharedContext: Context
): Promise<TAuthProvider[]> {
return await this.authProviderService_.create(data, sharedContext)
}
updateAuthProvider(
data: AuthTypes.UpdateAuthProviderDTO[],
sharedContext?: Context
): Promise<AuthProviderDTO[]>
updateAuthProvider(
data: AuthTypes.UpdateAuthProviderDTO,
sharedContext?: Context
): Promise<AuthProviderDTO>
@InjectManager("baseRepository_")
async updateAuthProvider(
data:
| AuthTypes.UpdateAuthProviderDTO[]
| AuthTypes.UpdateAuthProviderDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<
AuthTypes.AuthProviderDTO | AuthTypes.AuthProviderDTO[]
> {
const input = Array.isArray(data) ? data : [data]
const providers = await this.updateAuthProvider_(input, sharedContext)
const serializedProviders = await this.baseRepository_.serialize<
AuthTypes.AuthProviderDTO[]
>(providers, {
populate: true,
})
return Array.isArray(data) ? serializedProviders : serializedProviders[0]
}
async updateAuthProvider_(
data: AuthTypes.UpdateAuthProviderDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<TAuthProvider[]> {
return await this.authProviderService_.update(data, sharedContext)
}
@InjectTransactionManager("baseRepository_")
async deleteAuthProvider(
ids: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
await this.authProviderService_.delete(ids, sharedContext)
}
@InjectManager("baseRepository_")
async retrieveAuthUser(
id: string,
config: FindConfig<AuthUserDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<AuthUserDTO> {
const authUser = await this.authUserService_.retrieve(
id,
config,
sharedContext
)
return await this.baseRepository_.serialize<AuthTypes.AuthUserDTO>(
authUser,
{
exclude: ["password_hash"],
}
)
}
@InjectManager("baseRepository_")
async listAuthUsers(
filters: FilterableAuthUserProps = {},
config: FindConfig<AuthUserDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<AuthUserDTO[]> {
const authUsers = await this.authUserService_.list(
filters,
config,
sharedContext
)
return await this.baseRepository_.serialize<
AuthTypes.AuthUserDTO[]
>(authUsers, {
populate: true,
})
}
@InjectManager("baseRepository_")
async listAndCountAuthUsers(
filters: FilterableAuthUserProps = {},
config: FindConfig<AuthUserDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<[AuthUserDTO[], number]> {
const [authUsers, count] = await this.authUserService_.listAndCount(
filters,
config,
sharedContext
)
return [
await this.baseRepository_.serialize<AuthTypes.AuthUserDTO[]>(
authUsers,
{
populate: true,
}
),
count,
]
}
createAuthUser(
data: CreateAuthUserDTO[],
sharedContext?: Context
): Promise<AuthUserDTO[]>
createAuthUser(
data: CreateAuthUserDTO,
sharedContext?: Context
): Promise<AuthUserDTO>
@InjectManager("baseRepository_")
async createAuthUser(
data: CreateAuthUserDTO[] | CreateAuthUserDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<
AuthTypes.AuthUserDTO | AuthTypes.AuthUserDTO[]
> {
const input = Array.isArray(data) ? data : [data]
const authUsers = await this.createAuthUsers_(input, sharedContext)
const serializedUsers = await this.baseRepository_.serialize<
AuthTypes.AuthUserDTO[]
>(authUsers, {
populate: true,
})
return Array.isArray(data) ? serializedUsers : serializedUsers[0]
}
@InjectTransactionManager("baseRepository_")
protected async createAuthUsers_(
data: CreateAuthUserDTO[],
@MedusaContext() sharedContext: Context
): Promise<TAuthUser[]> {
return await this.authUserService_.create(data, sharedContext)
}
updateAuthUser(
data: UpdateAuthUserDTO[],
sharedContext?: Context
): Promise<AuthUserDTO[]>
updateAuthUser(
data: UpdateAuthUserDTO,
sharedContext?: Context
): Promise<AuthUserDTO>
@InjectManager("baseRepository_")
async updateAuthUser(
data: UpdateAuthUserDTO | UpdateAuthUserDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<
AuthTypes.AuthUserDTO | AuthTypes.AuthUserDTO[]
> {
const input = Array.isArray(data) ? data : [data]
const updatedUsers = await this.updateAuthUsers_(input, sharedContext)
const serializedUsers = await this.baseRepository_.serialize<
AuthTypes.AuthUserDTO[]
>(updatedUsers, {
populate: true,
})
return Array.isArray(data) ? serializedUsers : serializedUsers[0]
}
@InjectTransactionManager("baseRepository_")
protected async updateAuthUsers_(
data: UpdateAuthUserDTO[],
@MedusaContext() sharedContext: Context
): Promise<TAuthUser[]> {
return await this.authUserService_.update(data, sharedContext)
}
@InjectTransactionManager("baseRepository_")
async deleteAuthUser(
ids: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
await this.authUserService_.delete(ids, sharedContext)
}
protected getRegisteredAuthenticationProvider(
provider: string,
{ scope }: AuthenticationInput
): AbstractAuthModuleProvider {
let containerProvider: AbstractAuthModuleProvider
try {
containerProvider = this.__container__[`auth_provider_${provider}`]
} catch (error) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`AuthenticationProvider with for provider: ${provider} wasn't registered in the module. Have you configured your options correctly?`
)
}
containerProvider.validateScope(scope)
return containerProvider
}
async authenticate(
provider: string,
authenticationData: AuthenticationInput
): Promise<AuthenticationResponse> {
try {
await this.retrieveAuthProvider(provider, {})
const registeredProvider = this.getRegisteredAuthenticationProvider(
provider,
authenticationData
)
return await registeredProvider.authenticate(authenticationData)
} catch (error) {
return { success: false, error: error.message }
}
}
async validateCallback(
provider: string,
authenticationData: AuthenticationInput
): Promise<AuthenticationResponse> {
try {
await this.retrieveAuthProvider(provider, {})
const registeredProvider = this.getRegisteredAuthenticationProvider(
provider,
authenticationData
)
return await registeredProvider.validateCallback(authenticationData)
} catch (error) {
return { success: false, error: error.message }
}
}
private async createProvidersOnLoad() {
const providersToLoad = this.__container__["auth_providers"]
const providers = await this.authProviderService_.list({
provider: providersToLoad.map((p) => p.provider),
})
const loadedProvidersMap = new Map(providers.map((p) => [p.provider, p]))
const providersToCreate: ServiceTypes.CreateAuthProviderDTO[] = []
for (const provider of providersToLoad) {
if (loadedProvidersMap.has(provider.provider)) {
continue
}
providersToCreate.push({
provider: provider.provider,
name: provider.displayName,
})
}
await this.authProviderService_.create(providersToCreate)
}
}
@@ -0,0 +1,24 @@
import { DAL } from "@medusajs/types"
import { ModulesSdkUtils } from "@medusajs/utils"
import { AuthProvider } from "@models"
import { ServiceTypes } from "@types"
type InjectedDependencies = {
authProviderRepository: DAL.RepositoryService
}
export default class AuthProviderService<
TEntity extends AuthProvider = AuthProvider
> extends ModulesSdkUtils.abstractServiceFactory<
InjectedDependencies,
{
create: ServiceTypes.CreateAuthProviderDTO
update: ServiceTypes.UpdateAuthProviderDTO
}
>(AuthProvider)<TEntity> {
constructor(container: InjectedDependencies) {
// @ts-ignore
super(...arguments)
}
}
+57
View File
@@ -0,0 +1,57 @@
import { AuthTypes, Context, DAL, FindConfig } from "@medusajs/types"
import {
InjectManager,
MedusaContext,
MedusaError,
ModulesSdkUtils,
} from "@medusajs/utils"
import { AuthUser } from "@models"
import { ServiceTypes, RepositoryTypes } from "@types"
type InjectedDependencies = {
authUserRepository: DAL.RepositoryService
}
export default class AuthUserService<
TEntity extends AuthUser = AuthUser
> extends ModulesSdkUtils.abstractServiceFactory<
InjectedDependencies,
{
create: ServiceTypes.CreateAuthUserDTO
}
>(AuthUser)<TEntity> {
protected readonly authUserRepository_: RepositoryTypes.IAuthUserRepository<TEntity>
constructor(container: InjectedDependencies) {
// @ts-ignore
super(...arguments)
this.authUserRepository_ = container.authUserRepository
}
@InjectManager("authUserRepository_")
async retrieveByProviderAndEntityId<
TEntityMethod = AuthTypes.AuthUserDTO
>(
entityId: string,
provider: string,
config: FindConfig<TEntityMethod> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity> {
const queryConfig = ModulesSdkUtils.buildQuery<TEntity>(
{ entity_id: entityId, provider },
{ ...config, take: 1 }
)
const [result] = await this.authUserRepository_.find(
queryConfig,
sharedContext
)
if (!result) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`AuthUser with entity_id: "${entityId}" and provider: "${provider}" not found`
)
}
return result
}
}
+3
View File
@@ -0,0 +1,3 @@
export { default as AuthModuleService } from "./auth-module"
export { default as AuthProviderService } from "./auth-provider"
export { default as AuthUserService } from "./auth-user"
+8
View File
@@ -0,0 +1,8 @@
import { Logger } from "@medusajs/types"
export type InitializeModuleInjectableDependencies = {
logger?: Logger
}
export * as RepositoryTypes from "./repositories"
export * as ServiceTypes from "./services"
@@ -0,0 +1,26 @@
import { AuthProvider } from "@models"
export type CreateAuthProviderDTO = {
provider: string
name: string
domain?: ProviderDomain
is_active?: boolean
config?: Record<string, unknown>
}
export type UpdateAuthProviderDTO = {
update: {
provider: string
name?: string
domain?: ProviderDomain
is_active?: boolean
config?: Record<string, unknown>
}
provider: AuthProvider
}
export enum ProviderDomain {
ALL = "all",
STORE = "store",
ADMIN = "admin",
}
@@ -0,0 +1,19 @@
import { AuthUser } from "@models"
export type CreateAuthUserDTO = {
provider_id: string
entity_id: string
provider_metadata?: Record<string, unknown>
user_metadata?: Record<string, unknown>
app_metadata?: Record<string, unknown>
}
export type UpdateAuthUserDTO = {
update: {
id: string
provider_metadata?: Record<string, unknown>
user_metadata?: Record<string, unknown>
app_metadata?: Record<string, unknown>
}
user: AuthUser
}
@@ -0,0 +1,28 @@
import { AuthProvider, AuthUser } from "@models"
import { CreateAuthProviderDTO, UpdateAuthProviderDTO } from "./auth-provider"
import { DAL } from "@medusajs/types"
import { CreateAuthUserDTO, UpdateAuthUserDTO } from "./auth-user"
export * from "./auth-user"
export * from "./auth-provider"
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface IAuthProviderRepository<
TEntity extends AuthProvider = AuthProvider
> extends DAL.RepositoryService<
TEntity,
{
create: CreateAuthProviderDTO
update: UpdateAuthProviderDTO
}
> {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface IAuthUserRepository<TEntity extends AuthUser = AuthUser>
extends DAL.RepositoryService<
TEntity,
{
create: CreateAuthUserDTO
update: UpdateAuthUserDTO
}
> {}
@@ -0,0 +1,31 @@
export type AuthProviderDTO = {
provider: string
name: string
domain: ProviderDomain
is_active: boolean
config: Record<string, unknown>
}
export type CreateAuthProviderDTO = {
provider: string
name: string
domain?: ProviderDomain
is_active?: boolean
config?: Record<string, unknown>
}
export type UpdateAuthProviderDTO = {
provider: string
name?: string
domain?: ProviderDomain
is_active?: boolean
config?: Record<string, unknown>
}
export enum ProviderDomain {
ALL = "all",
STORE = "store",
ADMIN = "admin",
}
export type FilterableAuthProviderProps = {}
@@ -0,0 +1,28 @@
import { AuthProviderDTO } from "./auth-provider"
export type AuthUserDTO = {
id: string
provider_id: string
entity_id: string
provider: AuthProviderDTO
provider_metadata?: Record<string, unknown>
user_metadata: Record<string, unknown>
app_metadata: Record<string, unknown>
}
export type CreateAuthUserDTO = {
entity_id: string
provider_id: string
provider_metadata?: Record<string, unknown>
user_metadata?: Record<string, unknown>
app_metadata?: Record<string, unknown>
}
export type UpdateAuthUserDTO = {
id: string
provider_metadata?: Record<string, unknown>
user_metadata?: Record<string, unknown>
app_metadata?: Record<string, unknown>
}
export type FilterableAuthUserProps = {}
@@ -0,0 +1,2 @@
export * from "./auth-user"
export * from "./auth-provider"