Feat(authentication): username password provider (#6052)
This commit is contained in:
1 parent
9d7ed9dbaf
commit
24bb26b81a
20 files changed
+423
-43
No files matched your search
@@ -6,13 +6,16 @@ export async function createAuthUsers(
|
|||||||
userData: any[] = [
|
userData: any[] = [
|
||||||
{
|
{
|
||||||
id: "test-id",
|
id: "test-id",
|
||||||
|
entity_id: "test-id",
|
||||||
provider: "manual",
|
provider: "manual",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "test-id-1",
|
id: "test-id-1",
|
||||||
|
entity_id: "test-id-1",
|
||||||
provider: "manual",
|
provider: "manual",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
entity_id: "test-id-2",
|
||||||
provider: "store",
|
provider: "store",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ describe("AuthUser Service", () => {
|
|||||||
{
|
{
|
||||||
id: "test",
|
id: "test",
|
||||||
provider_id: "manual",
|
provider_id: "manual",
|
||||||
|
entity_id: "test"
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -237,6 +237,7 @@ describe("AuthenticationModuleService - AuthUser", () => {
|
|||||||
{
|
{
|
||||||
id: "test",
|
id: "test",
|
||||||
provider_id: "manual",
|
provider_id: "manual",
|
||||||
|
entity_id: "test"
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
+24
-1
@@ -5,6 +5,7 @@ import { initialize } from "../../../../src"
|
|||||||
import { DB_URL } from "@medusajs/pricing/integration-tests/utils"
|
import { DB_URL } from "@medusajs/pricing/integration-tests/utils"
|
||||||
import { MedusaModule } from "@medusajs/modules-sdk"
|
import { MedusaModule } from "@medusajs/modules-sdk"
|
||||||
import { IAuthenticationModuleService } from "@medusajs/types"
|
import { IAuthenticationModuleService } from "@medusajs/types"
|
||||||
|
import { createAuthProviders } from "../../../__fixtures__/auth-provider"
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
@@ -22,6 +23,10 @@ describe("AuthenticationModuleService - AuthProvider", () => {
|
|||||||
schema: process.env.MEDUSA_PRICING_DB_SCHEMA,
|
schema: process.env.MEDUSA_PRICING_DB_SCHEMA,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if(service.__hooks?.onApplicationStart) {
|
||||||
|
await service.__hooks.onApplicationStart()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -30,7 +35,7 @@ describe("AuthenticationModuleService - AuthProvider", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("listAuthProviders", () => {
|
describe("listAuthProviders", () => {
|
||||||
it("should list default AuthProviders", async () => {
|
it("should list default AuthProviders registered by loaders", async () => {
|
||||||
const authProviders = await service.listAuthProviders()
|
const authProviders = await service.listAuthProviders()
|
||||||
const serialized = JSON.parse(JSON.stringify(authProviders))
|
const serialized = JSON.parse(JSON.stringify(authProviders))
|
||||||
|
|
||||||
@@ -42,4 +47,22 @@ describe("AuthenticationModuleService - AuthProvider", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("authenticate", () => {
|
||||||
|
it("authenticate validates that a provider is registered in container", async () => {
|
||||||
|
await createAuthProviders(testManager, [
|
||||||
|
{
|
||||||
|
provider: "notRegistered",
|
||||||
|
name: "test",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const { success, error } = await service.authenticate("notRegistered", {})
|
||||||
|
|
||||||
|
expect(success).toBe(false)
|
||||||
|
expect(error).toEqual(
|
||||||
|
"AuthenticationProvider with for provider: notRegistered wasn't registered in the module. Have you configured your options correctly?"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
|
import Scrypt from "scrypt-kdf"
|
||||||
|
|
||||||
|
import { MikroOrmWrapper } from "../../../utils"
|
||||||
|
import { initialize } from "../../../../src"
|
||||||
|
import { DB_URL } from "@medusajs/pricing/integration-tests/utils"
|
||||||
|
import { MedusaModule } from "@medusajs/modules-sdk"
|
||||||
|
import { IAuthenticationModuleService } from "@medusajs/types"
|
||||||
|
import { createAuthUsers } from "../../../__fixtures__/auth-user"
|
||||||
|
import { createAuthProviders } from "../../../__fixtures__/auth-provider"
|
||||||
|
|
||||||
|
jest.setTimeout(30000)
|
||||||
|
const seedDefaultData = async (testManager) => {
|
||||||
|
await createAuthProviders(testManager)
|
||||||
|
await createAuthUsers(testManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("AuthenticationModuleService - AuthProvider", () => {
|
||||||
|
let service: IAuthenticationModuleService
|
||||||
|
let testManager: SqlEntityManager
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await MikroOrmWrapper.setupDatabase()
|
||||||
|
testManager = MikroOrmWrapper.forkManager()
|
||||||
|
|
||||||
|
service = await initialize({
|
||||||
|
database: {
|
||||||
|
clientUrl: DB_URL,
|
||||||
|
schema: process.env.MEDUSA_PRICING_DB_SCHEMA,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if(service.__hooks?.onApplicationStart) {
|
||||||
|
await service.__hooks.onApplicationStart()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await MikroOrmWrapper.clearDatabase()
|
||||||
|
MedusaModule.clearInstances()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("authenticate", () => {
|
||||||
|
it("authenticate validates that a provider is registered in container", async () => {
|
||||||
|
const password = "supersecret"
|
||||||
|
const email = "test@test.com"
|
||||||
|
const passwordHash = (
|
||||||
|
await Scrypt.kdf(password, { logN: 15, r: 8, p: 1 })
|
||||||
|
).toString("base64")
|
||||||
|
|
||||||
|
await seedDefaultData(testManager)
|
||||||
|
await createAuthUsers(testManager, [
|
||||||
|
// Add authenticated user
|
||||||
|
{
|
||||||
|
provider: "usernamePassword",
|
||||||
|
entity_id: email,
|
||||||
|
provider_metadata: {
|
||||||
|
password: passwordHash,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const res = await service.authenticate("usernamePassword", {
|
||||||
|
body: {
|
||||||
|
email: "test@test.com",
|
||||||
|
password: password,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res).toEqual({
|
||||||
|
success: true,
|
||||||
|
authUser: expect.objectContaining({
|
||||||
|
entity_id: email,
|
||||||
|
provider_metadata: {
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails when no password is given", async () => {
|
||||||
|
const email = "test@test.com"
|
||||||
|
|
||||||
|
await seedDefaultData(testManager)
|
||||||
|
|
||||||
|
const res = await service.authenticate("usernamePassword", {
|
||||||
|
body: { email: "test@test.com" },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res).toEqual({
|
||||||
|
success: false,
|
||||||
|
error: "Password should be a string",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails when no email is given", async () => {
|
||||||
|
await seedDefaultData(testManager)
|
||||||
|
|
||||||
|
const res = await service.authenticate("usernamePassword", {
|
||||||
|
body: { password: "supersecret" },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res).toEqual({
|
||||||
|
success: false,
|
||||||
|
error: "Email should be a string",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails with an invalid password", async () => {
|
||||||
|
const password = "supersecret"
|
||||||
|
const email = "test@test.com"
|
||||||
|
const passwordHash = (
|
||||||
|
await Scrypt.kdf(password, { logN: 15, r: 8, p: 1 })
|
||||||
|
).toString("base64")
|
||||||
|
|
||||||
|
await seedDefaultData(testManager)
|
||||||
|
await createAuthUsers(testManager, [
|
||||||
|
// Add authenticated user
|
||||||
|
{
|
||||||
|
provider: "usernamePassword",
|
||||||
|
entity_id: email,
|
||||||
|
provider_metadata: {
|
||||||
|
password_hash: passwordHash,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const res = await service.authenticate("usernamePassword", {
|
||||||
|
body: {
|
||||||
|
email: "test@test.com",
|
||||||
|
password: "password",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res).toEqual({
|
||||||
|
success: false,
|
||||||
|
error: "Invalid email or password",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
"@mikro-orm/postgresql": "5.9.7",
|
"@mikro-orm/postgresql": "5.9.7",
|
||||||
"awilix": "^8.0.0",
|
"awilix": "^8.0.0",
|
||||||
"dotenv": "^16.1.4",
|
"dotenv": "^16.1.4",
|
||||||
"knex": "2.4.2"
|
"knex": "2.4.2",
|
||||||
|
"scrypt-kdf": "^2.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
ExternalModuleDeclaration,
|
ExternalModuleDeclaration,
|
||||||
InternalModuleDeclaration,
|
InternalModuleDeclaration,
|
||||||
MedusaModule,
|
|
||||||
MODULE_PACKAGE_NAMES,
|
MODULE_PACKAGE_NAMES,
|
||||||
|
MedusaModule,
|
||||||
Modules,
|
Modules,
|
||||||
} from "@medusajs/modules-sdk"
|
} from "@medusajs/modules-sdk"
|
||||||
import { IAuthenticationModuleService, ModulesSdkTypes } from "@medusajs/types"
|
import { IAuthenticationModuleService, ModulesSdkTypes } from "@medusajs/types"
|
||||||
import { moduleDefinition } from "../module-definition"
|
|
||||||
import { InitializeModuleInjectableDependencies } from "../types"
|
import { InitializeModuleInjectableDependencies } from "../types"
|
||||||
|
import { moduleDefinition } from "../module-definition"
|
||||||
|
|
||||||
export const initialize = async (
|
export const initialize = async (
|
||||||
options?:
|
options?:
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { LoaderOptions, ModulesSdkTypes } from "@medusajs/types"
|
|
||||||
import { asClass } from "awilix"
|
|
||||||
import * as defaultProviders from "@providers"
|
import * as defaultProviders from "@providers"
|
||||||
import { AuthProviderService } from "@services"
|
|
||||||
import { ServiceTypes } from "@types"
|
import { LoaderOptions, ModulesSdkTypes } from "@medusajs/types"
|
||||||
|
|
||||||
|
import { AwilixContainer, ClassOrFunctionReturning, Resolver, asClass, asFunction, asValue } from "awilix"
|
||||||
|
|
||||||
export default async ({
|
export default async ({
|
||||||
container,
|
container,
|
||||||
options,
|
|
||||||
}: LoaderOptions<
|
}: LoaderOptions<
|
||||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||||
@@ -17,33 +16,22 @@ export default async ({
|
|||||||
|
|
||||||
const providersToLoad = Object.values(defaultProviders)
|
const providersToLoad = Object.values(defaultProviders)
|
||||||
|
|
||||||
const authProviderService: AuthProviderService =
|
|
||||||
container.cradle["authProviderService"]
|
|
||||||
|
|
||||||
const providers = await authProviderService.list({
|
|
||||||
provider: providersToLoad.map((p) => p.PROVIDER),
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadedProviders = new Map(providers.map((p) => [p.provider, p]))
|
|
||||||
|
|
||||||
const providersToCreate: ServiceTypes.CreateAuthProviderDTO[] = []
|
|
||||||
|
|
||||||
for (const provider of providersToLoad) {
|
for (const provider of providersToLoad) {
|
||||||
container.registerAdd("providers", asClass(provider).singleton())
|
container.register({
|
||||||
|
[`auth_provider_${provider.PROVIDER}`]: asClass(provider).singleton(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
container.register({
|
container.register({
|
||||||
[`provider_${provider.PROVIDER}`]: asClass(provider).singleton(),
|
[`auth_providers`]: asArray(providersToLoad),
|
||||||
})
|
|
||||||
|
|
||||||
if (loadedProviders.has(provider.PROVIDER)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
providersToCreate.push({
|
|
||||||
provider: provider.PROVIDER,
|
|
||||||
name: provider.DISPLAY_NAME,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await authProviderService.create(providersToCreate)
|
function asArray(
|
||||||
|
resolvers: (ClassOrFunctionReturning<unknown> | Resolver<unknown>)[]
|
||||||
|
): { resolve: (container: AwilixContainer) => unknown[] } {
|
||||||
|
return {
|
||||||
|
resolve: (container: AwilixContainer) =>
|
||||||
|
resolvers.map((resolver) => container.build(resolver)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -77,6 +77,15 @@
|
|||||||
"nullable": false,
|
"nullable": false,
|
||||||
"mappedType": "text"
|
"mappedType": "text"
|
||||||
},
|
},
|
||||||
|
"entity_id": {
|
||||||
|
"name": "entity_id",
|
||||||
|
"type": "text",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"mappedType": "text"
|
||||||
|
},
|
||||||
"provider_id": {
|
"provider_id": {
|
||||||
"name": "provider_id",
|
"name": "provider_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -117,6 +126,16 @@
|
|||||||
"name": "auth_user",
|
"name": "auth_user",
|
||||||
"schema": "public",
|
"schema": "public",
|
||||||
"indexes": [
|
"indexes": [
|
||||||
|
{
|
||||||
|
"keyName": "IDX_auth_user_provider_entity_id",
|
||||||
|
"columnNames": [
|
||||||
|
"provider_id",
|
||||||
|
"entity_id"
|
||||||
|
],
|
||||||
|
"composite": true,
|
||||||
|
"primary": false,
|
||||||
|
"unique": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"keyName": "auth_user_pkey",
|
"keyName": "auth_user_pkey",
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Migration } from '@mikro-orm/migrations';
|
||||||
|
|
||||||
|
export class Migration20240115092929 extends Migration {
|
||||||
|
|
||||||
|
async up(): Promise<void> {
|
||||||
|
this.addSql('alter table "auth_user" add column "entity_id" text not null;');
|
||||||
|
this.addSql('alter table "auth_user" add constraint "IDX_auth_user_provider_entity_id" unique ("provider_id", "entity_id");');
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(): Promise<void> {
|
||||||
|
this.addSql('alter table "auth_user" drop constraint "IDX_auth_user_provider_entity_id";');
|
||||||
|
this.addSql('alter table "auth_user" drop column "entity_id";');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,25 +1,32 @@
|
|||||||
import { generateEntityId } from "@medusajs/utils"
|
|
||||||
import {
|
import {
|
||||||
BeforeCreate,
|
BeforeCreate,
|
||||||
Cascade,
|
Cascade,
|
||||||
Entity,
|
Entity,
|
||||||
|
Index,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
OnInit,
|
OnInit,
|
||||||
OptionalProps,
|
OptionalProps,
|
||||||
PrimaryKey,
|
PrimaryKey,
|
||||||
Property,
|
Property,
|
||||||
|
Unique,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
|
|
||||||
import AuthProvider from "./auth-provider"
|
import AuthProvider from "./auth-provider"
|
||||||
|
import { generateEntityId } from "@medusajs/utils"
|
||||||
|
|
||||||
type OptionalFields = "provider_metadata" | "app_metadata" | "user_metadata"
|
type OptionalFields = "provider_metadata" | "app_metadata" | "user_metadata"
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
|
@Unique({ properties: ["provider","entity_id" ], name: "IDX_auth_user_provider_entity_id" })
|
||||||
export default class AuthUser {
|
export default class AuthUser {
|
||||||
[OptionalProps]: OptionalFields
|
[OptionalProps]: OptionalFields
|
||||||
|
|
||||||
@PrimaryKey({ columnType: "text" })
|
@PrimaryKey({ columnType: "text" })
|
||||||
id!: string
|
id!: string
|
||||||
|
|
||||||
|
@Property({ columnType: "text" })
|
||||||
|
entity_id: string
|
||||||
|
|
||||||
@ManyToOne(() => AuthProvider, {
|
@ManyToOne(() => AuthProvider, {
|
||||||
joinColumn: "provider",
|
joinColumn: "provider",
|
||||||
fieldName: "provider_id",
|
fieldName: "provider_id",
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
|
import {
|
||||||
|
AbstractAuthenticationModuleProvider,
|
||||||
|
AuthenticationResponse,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
|
||||||
import { AuthUserService } from "@services"
|
import { AuthUserService } from "@services"
|
||||||
import { AbstractAuthenticationModuleProvider } from "@medusajs/types"
|
import Scrypt from "scrypt-kdf"
|
||||||
|
import { isString } from "@medusajs/utils"
|
||||||
|
|
||||||
class UsernamePasswordProvider extends AbstractAuthenticationModuleProvider {
|
class UsernamePasswordProvider extends AbstractAuthenticationModuleProvider {
|
||||||
public static PROVIDER = "usernamePassword"
|
public static PROVIDER = "usernamePassword"
|
||||||
@@ -13,8 +19,48 @@ class UsernamePasswordProvider extends AbstractAuthenticationModuleProvider {
|
|||||||
this.authUserSerivce_ = AuthUserService
|
this.authUserSerivce_ = AuthUserService
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticate(userData: Record<string, unknown>) {
|
async authenticate(
|
||||||
return {}
|
userData: Record<string, any>
|
||||||
|
): 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,
|
||||||
|
UsernamePasswordProvider.PROVIDER
|
||||||
|
)
|
||||||
|
|
||||||
|
const password_hash = authUser.provider_metadata?.password
|
||||||
|
|
||||||
|
if (isString(password_hash)) {
|
||||||
|
const buf = Buffer.from(password_hash, "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",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { DAL } from "@medusajs/types"
|
import { AuthenticationTypes, Context, DAL, FindConfig } from "@medusajs/types"
|
||||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
import {
|
||||||
|
InjectManager,
|
||||||
|
MedusaContext,
|
||||||
|
MedusaError,
|
||||||
|
ModulesSdkUtils,
|
||||||
|
} from "@medusajs/utils"
|
||||||
import { AuthUser } from "@models"
|
import { AuthUser } from "@models"
|
||||||
|
import { ServiceTypes, RepositoryTypes } from "@types"
|
||||||
import { ServiceTypes } from "@types"
|
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
authUserRepository: DAL.RepositoryService
|
authUserRepository: DAL.RepositoryService
|
||||||
@@ -16,8 +20,38 @@ export default class AuthUserService<
|
|||||||
create: ServiceTypes.CreateAuthUserDTO
|
create: ServiceTypes.CreateAuthUserDTO
|
||||||
}
|
}
|
||||||
>(AuthUser)<TEntity> {
|
>(AuthUser)<TEntity> {
|
||||||
|
protected readonly authUserRepository_: RepositoryTypes.IAuthUserRepository<TEntity>
|
||||||
constructor(container: InjectedDependencies) {
|
constructor(container: InjectedDependencies) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
super(...arguments)
|
super(...arguments)
|
||||||
|
this.authUserRepository_ = container.authUserRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectManager("authUserRepository_")
|
||||||
|
async retrieveByProviderAndEntityId<
|
||||||
|
TEntityMethod = AuthenticationTypes.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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
|
AbstractAuthenticationModuleProvider,
|
||||||
|
AuthenticationResponse,
|
||||||
AuthenticationTypes,
|
AuthenticationTypes,
|
||||||
Context,
|
Context,
|
||||||
DAL,
|
DAL,
|
||||||
FindConfig,
|
FindConfig,
|
||||||
InternalModuleDeclaration,
|
InternalModuleDeclaration,
|
||||||
|
MedusaContainer,
|
||||||
ModuleJoinerConfig,
|
ModuleJoinerConfig,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
|
|
||||||
@@ -11,10 +14,12 @@ import { AuthProvider, AuthUser } from "@models"
|
|||||||
|
|
||||||
import { joinerConfig } from "../joiner-config"
|
import { joinerConfig } from "../joiner-config"
|
||||||
import { AuthProviderService, AuthUserService } from "@services"
|
import { AuthProviderService, AuthUserService } from "@services"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
InjectManager,
|
InjectManager,
|
||||||
InjectTransactionManager,
|
InjectTransactionManager,
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
|
MedusaError,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
AuthProviderDTO,
|
AuthProviderDTO,
|
||||||
@@ -25,6 +30,7 @@ import {
|
|||||||
FilterableAuthUserProps,
|
FilterableAuthUserProps,
|
||||||
UpdateAuthUserDTO,
|
UpdateAuthUserDTO,
|
||||||
} from "@medusajs/types/dist/authentication/common"
|
} from "@medusajs/types/dist/authentication/common"
|
||||||
|
import { ServiceTypes } from "@types"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
baseRepository: DAL.RepositoryService
|
baseRepository: DAL.RepositoryService
|
||||||
@@ -37,6 +43,15 @@ export default class AuthenticationModuleService<
|
|||||||
TAuthProvider extends AuthProvider = AuthProvider
|
TAuthProvider extends AuthProvider = AuthProvider
|
||||||
> implements AuthenticationTypes.IAuthenticationModuleService
|
> implements AuthenticationTypes.IAuthenticationModuleService
|
||||||
{
|
{
|
||||||
|
__joinerConfig(): ModuleJoinerConfig {
|
||||||
|
return joinerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
__hooks = {
|
||||||
|
onApplicationStart: async () => await this.createProvidersOnLoad(),
|
||||||
|
}
|
||||||
|
|
||||||
|
protected __container__: MedusaContainer
|
||||||
protected baseRepository_: DAL.RepositoryService
|
protected baseRepository_: DAL.RepositoryService
|
||||||
|
|
||||||
protected authUserService_: AuthUserService<TAuthUser>
|
protected authUserService_: AuthUserService<TAuthUser>
|
||||||
@@ -50,6 +65,7 @@ export default class AuthenticationModuleService<
|
|||||||
}: InjectedDependencies,
|
}: InjectedDependencies,
|
||||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||||
) {
|
) {
|
||||||
|
this.__container__ = arguments[0]
|
||||||
this.baseRepository_ = baseRepository
|
this.baseRepository_ = baseRepository
|
||||||
this.authUserService_ = authUserService
|
this.authUserService_ = authUserService
|
||||||
this.authProviderService_ = authProviderService
|
this.authProviderService_ = authProviderService
|
||||||
@@ -336,7 +352,64 @@ export default class AuthenticationModuleService<
|
|||||||
await this.authUserService_.delete(ids, sharedContext)
|
await this.authUserService_.delete(ids, sharedContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
__joinerConfig(): ModuleJoinerConfig {
|
protected getRegisteredAuthenticationProvider(
|
||||||
return joinerConfig
|
provider: string
|
||||||
|
): AbstractAuthenticationModuleProvider {
|
||||||
|
let containerProvider: AbstractAuthenticationModuleProvider
|
||||||
|
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?`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return containerProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
async authenticate(
|
||||||
|
provider: string,
|
||||||
|
authenticationData: Record<string, unknown>,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<AuthenticationResponse> {
|
||||||
|
let registeredProvider
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.retrieveAuthProvider(provider, {})
|
||||||
|
|
||||||
|
registeredProvider = this.getRegisteredAuthenticationProvider(provider)
|
||||||
|
|
||||||
|
return await registeredProvider.authenticate(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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ import { AuthUser } from "@models"
|
|||||||
|
|
||||||
export type CreateAuthUserDTO = {
|
export type CreateAuthUserDTO = {
|
||||||
provider_id: string
|
provider_id: string
|
||||||
|
entity_id: string
|
||||||
provider_metadata?: Record<string, unknown>
|
provider_metadata?: Record<string, unknown>
|
||||||
user_metadata?: Record<string, unknown>
|
user_metadata?: Record<string, unknown>
|
||||||
app_metadata?: Record<string, unknown>
|
app_metadata?: Record<string, unknown>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { AuthProviderDTO } from "./auth-provider"
|
|||||||
export type AuthUserDTO = {
|
export type AuthUserDTO = {
|
||||||
id: string
|
id: string
|
||||||
provider_id: string
|
provider_id: string
|
||||||
|
entity_id: string
|
||||||
provider: AuthProviderDTO
|
provider: AuthProviderDTO
|
||||||
provider_metadata?: Record<string, unknown>
|
provider_metadata?: Record<string, unknown>
|
||||||
user_metadata: Record<string, unknown>
|
user_metadata: Record<string, unknown>
|
||||||
@@ -10,6 +11,7 @@ export type AuthUserDTO = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CreateAuthUserDTO = {
|
export type CreateAuthUserDTO = {
|
||||||
|
entity_id: string
|
||||||
provider_id: string
|
provider_id: string
|
||||||
provider_metadata?: Record<string, unknown>
|
provider_metadata?: Record<string, unknown>
|
||||||
user_metadata?: Record<string, unknown>
|
user_metadata?: Record<string, unknown>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { AuthProviderDTO } from "./auth-provider"
|
|||||||
export type AuthUserDTO = {
|
export type AuthUserDTO = {
|
||||||
id: string
|
id: string
|
||||||
provider_id: string
|
provider_id: string
|
||||||
|
entity_id: string
|
||||||
provider: AuthProviderDTO
|
provider: AuthProviderDTO
|
||||||
provider_metadata?: Record<string, unknown>
|
provider_metadata?: Record<string, unknown>
|
||||||
user_metadata: Record<string, unknown>
|
user_metadata: Record<string, unknown>
|
||||||
@@ -12,6 +13,7 @@ export type AuthUserDTO = {
|
|||||||
|
|
||||||
export type CreateAuthUserDTO = {
|
export type CreateAuthUserDTO = {
|
||||||
provider_id: string
|
provider_id: string
|
||||||
|
entity_id: string
|
||||||
provider_metadata?: Record<string, unknown>
|
provider_metadata?: Record<string, unknown>
|
||||||
user_metadata?: Record<string, unknown>
|
user_metadata?: Record<string, unknown>
|
||||||
app_metadata?: Record<string, unknown>
|
app_metadata?: Record<string, unknown>
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
|
import { AuthUserDTO } from "./common"
|
||||||
|
|
||||||
export abstract class AbstractAuthenticationModuleProvider {
|
export abstract class AbstractAuthenticationModuleProvider {
|
||||||
public static PROVIDER: string
|
public static PROVIDER: string
|
||||||
public static DISPLAY_NAME: string
|
public static DISPLAY_NAME: string
|
||||||
|
|
||||||
|
public get provider() {
|
||||||
|
return (this.constructor as Function & { PROVIDER: string}).PROVIDER
|
||||||
|
}
|
||||||
|
|
||||||
|
public get displayName() {
|
||||||
|
return (this.constructor as Function & { DISPLAY_NAME: string}).DISPLAY_NAME
|
||||||
|
}
|
||||||
|
|
||||||
abstract authenticate(
|
abstract authenticate(
|
||||||
data: Record<string, unknown>
|
data: Record<string, unknown>
|
||||||
): Promise<Record<string, unknown>>
|
): Promise<AuthenticationResponse>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthenticationResponse = {
|
||||||
|
success: boolean
|
||||||
|
authUser?: AuthUserDTO
|
||||||
|
error?: string
|
||||||
}
|
}
|
||||||
@@ -11,8 +11,14 @@ import {
|
|||||||
} from "./common"
|
} from "./common"
|
||||||
import { FindConfig } from "../common"
|
import { FindConfig } from "../common"
|
||||||
import { Context } from "../shared-context"
|
import { Context } from "../shared-context"
|
||||||
|
import { AuthenticationResponse } from "./provider"
|
||||||
|
|
||||||
export interface IAuthenticationModuleService extends IModuleService {
|
export interface IAuthenticationModuleService extends IModuleService {
|
||||||
|
authenticate(
|
||||||
|
provider: string,
|
||||||
|
providerData: Record<string, unknown>
|
||||||
|
): Promise<AuthenticationResponse>
|
||||||
|
|
||||||
retrieveAuthProvider(
|
retrieveAuthProvider(
|
||||||
provider: string,
|
provider: string,
|
||||||
config?: FindConfig<AuthProviderDTO>,
|
config?: FindConfig<AuthProviderDTO>,
|
||||||
|
|||||||
Reference in new issue
Block a user