Revamp the authentication setup (#7419)
* feat: Add email pass authentication provider package * feat: Revamp auth module and remove concept of scope * feat: Revamp the auth module to be more standardized in how providers are loaded * feat: Switch from scope to actor type for authentication * feat: Add support for per-actor auth methods * feat: Add emailpass auth provider by default * fix: Add back app_metadata in auth module
This commit is contained in:
@@ -1,21 +1,26 @@
|
||||
import * as defaultProviders from "@providers"
|
||||
import EmailPassProvider from "@medusajs/auth-emailpass"
|
||||
|
||||
import { LoaderOptions, ModulesSdkTypes, ModuleProvider } from "@medusajs/types"
|
||||
import { Lifetime, asFunction, asValue } from "awilix"
|
||||
import { moduleProviderLoader } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
AuthModuleProviderConfig,
|
||||
AuthProviderScope,
|
||||
LoaderOptions,
|
||||
ModulesSdkTypes,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
AwilixContainer,
|
||||
ClassOrFunctionReturning,
|
||||
Constructor,
|
||||
Resolver,
|
||||
asClass,
|
||||
} from "awilix"
|
||||
AuthIdentifiersRegistrationName,
|
||||
AuthProviderRegistrationPrefix,
|
||||
} from "@types"
|
||||
|
||||
type AuthModuleProviders = {
|
||||
providers: AuthModuleProviderConfig[]
|
||||
const registrationFn = async (klass, container, pluginOptions) => {
|
||||
Object.entries(pluginOptions.config || []).map(([name, config]) => {
|
||||
container.register({
|
||||
[AuthProviderRegistrationPrefix + name]: asFunction(
|
||||
(cradle) => new klass(cradle, config),
|
||||
{
|
||||
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
|
||||
}
|
||||
),
|
||||
})
|
||||
|
||||
container.registerAdd(AuthIdentifiersRegistrationName, asValue(name))
|
||||
})
|
||||
}
|
||||
|
||||
export default async ({
|
||||
@@ -25,48 +30,28 @@ export default async ({
|
||||
(
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
) &
|
||||
AuthModuleProviders
|
||||
) & { providers: ModuleProvider[] }
|
||||
>): Promise<void> => {
|
||||
const providerMap = new Map(
|
||||
options?.providers?.map((provider) => [provider.name, provider.scopes]) ??
|
||||
[]
|
||||
)
|
||||
// TODO: Temporary settings used by the starter, remove once the auth module is updated
|
||||
const isLegacyOptions =
|
||||
options?.providers?.length && !!(options?.providers[0] as any)?.name
|
||||
|
||||
// if(options?.providers?.length) {
|
||||
// TODO: implement plugin provider registration
|
||||
// }
|
||||
// Note: For now we want to inject some providers out of the box
|
||||
const providerConfig = [
|
||||
{
|
||||
resolve: EmailPassProvider,
|
||||
options: {
|
||||
config: {
|
||||
emailpass: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
...(isLegacyOptions ? [] : options?.providers ?? []),
|
||||
]
|
||||
|
||||
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),
|
||||
await moduleProviderLoader({
|
||||
container,
|
||||
providers: providerConfig,
|
||||
registerServiceFn: registrationFn,
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,15 +31,6 @@
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"scope": {
|
||||
"name": "scope",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"user_metadata": {
|
||||
"name": "user_metadata",
|
||||
"type": "jsonb",
|
||||
@@ -72,8 +63,8 @@
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_auth_identity_provider_scope_entity_id",
|
||||
"columnNames": ["provider", "scope", "entity_id"],
|
||||
"keyName": "IDX_auth_identity_provider_entity_id",
|
||||
"columnNames": ["provider", "entity_id"],
|
||||
"composite": true,
|
||||
"primary": false,
|
||||
"unique": true
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240205025924 extends Migration {
|
||||
async up(): Promise<void> {
|
||||
this.addSql(
|
||||
'create table if not exists "auth_identity" ("id" text not null, "entity_id" text not null, "provider" text not null, "scope" text not null, "user_metadata" jsonb null, "app_metadata" jsonb not null, "provider_metadata" jsonb null, constraint "auth_identity_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table "auth_identity" add constraint "IDX_auth_identity_provider_scope_entity_id" unique ("provider", "scope", "entity_id");'
|
||||
)
|
||||
}
|
||||
|
||||
async down(): Promise<void> {
|
||||
this.addSql('drop table if exists "auth_identity" cascade;')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240205025928 extends Migration {
|
||||
async up(): Promise<void> {
|
||||
this.addSql(
|
||||
'create table if not exists "auth_identity" ("id" text not null, "entity_id" text not null, "provider" text not null, "user_metadata" jsonb null, "app_metadata" jsonb null, "provider_metadata" jsonb null, constraint "auth_identity_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table "auth_identity" add constraint "IDX_auth_identity_provider_entity_id" unique ("provider", "entity_id");'
|
||||
)
|
||||
|
||||
this.addSql('alter table "auth_identity" drop column if exists "scope";')
|
||||
this.addSql(
|
||||
`alter table "auth_identity" alter column "app_metadata" drop not null;`
|
||||
)
|
||||
}
|
||||
|
||||
async down(): Promise<void> {
|
||||
this.addSql('drop table if exists "auth_identity" cascade;')
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ type OptionalFields = "provider_metadata" | "app_metadata" | "user_metadata"
|
||||
|
||||
@Entity()
|
||||
@Unique({
|
||||
properties: ["provider", "scope", "entity_id"],
|
||||
name: "IDX_auth_identity_provider_scope_entity_id",
|
||||
properties: ["provider", "entity_id"],
|
||||
name: "IDX_auth_identity_provider_entity_id",
|
||||
})
|
||||
export default class AuthIdentity {
|
||||
[OptionalProps]: OptionalFields
|
||||
@@ -29,14 +29,11 @@ export default class AuthIdentity {
|
||||
@Property({ columnType: "text" })
|
||||
provider: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
scope: string
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
user_metadata: Record<string, unknown> | null
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
app_metadata: Record<string, unknown> = {}
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
app_metadata: Record<string, unknown> | null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
provider_metadata: Record<string, unknown> | null = null
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AuthModuleService } from "@services"
|
||||
import { ModuleExports } from "@medusajs/types"
|
||||
import { AuthModuleService } from "@services"
|
||||
import loadProviders from "./loaders/providers"
|
||||
|
||||
const service = AuthModuleService
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
import { AuthenticationInput, AuthenticationResponse } from "@medusajs/types"
|
||||
import {
|
||||
AbstractAuthModuleProvider,
|
||||
MedusaError,
|
||||
isString,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { AuthIdentityService } from "@services"
|
||||
import Scrypt from "scrypt-kdf"
|
||||
|
||||
class EmailPasswordProvider extends AbstractAuthModuleProvider {
|
||||
public static PROVIDER = "emailpass"
|
||||
public static DISPLAY_NAME = "Email/Password Authentication"
|
||||
|
||||
protected readonly authIdentitySerivce_: AuthIdentityService
|
||||
|
||||
constructor({
|
||||
authIdentityService,
|
||||
}: {
|
||||
authIdentityService: AuthIdentityService
|
||||
}) {
|
||||
super(arguments[0], {
|
||||
provider: EmailPasswordProvider.PROVIDER,
|
||||
displayName: EmailPasswordProvider.DISPLAY_NAME,
|
||||
})
|
||||
|
||||
this.authIdentitySerivce_ = authIdentityService
|
||||
}
|
||||
|
||||
private getHashConfig() {
|
||||
const scopeConfig = this.scopeConfig_.hashConfig as
|
||||
| Scrypt.ScryptParams
|
||||
| undefined
|
||||
|
||||
const defaultHashConfig = { logN: 15, r: 8, p: 1 }
|
||||
|
||||
// Return custom defined hash config or default hash parameters
|
||||
return scopeConfig ?? defaultHashConfig
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
}
|
||||
let authIdentity
|
||||
|
||||
try {
|
||||
authIdentity =
|
||||
await this.authIdentitySerivce_.retrieveByProviderAndEntityId(
|
||||
email,
|
||||
EmailPasswordProvider.PROVIDER
|
||||
)
|
||||
} catch (error) {
|
||||
if (error.type === MedusaError.Types.NOT_FOUND) {
|
||||
const password_hash = await Scrypt.kdf(password, this.getHashConfig())
|
||||
|
||||
const [createdAuthIdentity] = await this.authIdentitySerivce_.create([
|
||||
{
|
||||
entity_id: email,
|
||||
provider: EmailPasswordProvider.PROVIDER,
|
||||
scope: this.scope_,
|
||||
provider_metadata: {
|
||||
password: password_hash.toString("base64"),
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
return {
|
||||
success: true,
|
||||
authIdentity: JSON.parse(JSON.stringify(createdAuthIdentity)),
|
||||
}
|
||||
}
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
|
||||
const password_hash = authIdentity.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 authIdentity.provider_metadata!.password
|
||||
|
||||
return {
|
||||
success,
|
||||
authIdentity: JSON.parse(JSON.stringify(authIdentity)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: "Invalid email or password",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EmailPasswordProvider
|
||||
@@ -1,13 +1,12 @@
|
||||
import { AuthenticationInput, AuthenticationResponse } from "@medusajs/types"
|
||||
import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils"
|
||||
import { AuthIdentityService } from "@services"
|
||||
import jwt, { JwtPayload } from "jsonwebtoken"
|
||||
|
||||
import { AuthorizationCode } from "simple-oauth2"
|
||||
import url from "url"
|
||||
|
||||
type InjectedDependencies = {
|
||||
authIdentityService: AuthIdentityService
|
||||
authIdentityService: any
|
||||
}
|
||||
|
||||
type ProviderConfig = {
|
||||
@@ -18,15 +17,12 @@ type ProviderConfig = {
|
||||
}
|
||||
|
||||
class GoogleProvider extends AbstractAuthModuleProvider {
|
||||
public static PROVIDER = "google"
|
||||
public static DISPLAY_NAME = "Google Authentication"
|
||||
protected readonly authIdentityService_: any
|
||||
|
||||
protected readonly authIdentityService_: AuthIdentityService
|
||||
|
||||
constructor({ authIdentityService }: InjectedDependencies) {
|
||||
constructor({ authIdentityService }: InjectedDependencies, options: any) {
|
||||
super(arguments[0], {
|
||||
provider: GoogleProvider.PROVIDER,
|
||||
displayName: GoogleProvider.DISPLAY_NAME,
|
||||
provider: "google",
|
||||
displayName: "Google Authentication",
|
||||
})
|
||||
|
||||
this.authIdentityService_ = authIdentityService
|
||||
@@ -72,6 +68,9 @@ class GoogleProvider extends AbstractAuthModuleProvider {
|
||||
}
|
||||
|
||||
const code = req.query?.code ?? req.body?.code
|
||||
if (!code) {
|
||||
return { success: false, error: "No code provided" }
|
||||
}
|
||||
|
||||
return await this.validateCallbackToken(code, config)
|
||||
}
|
||||
@@ -89,16 +88,15 @@ class GoogleProvider extends AbstractAuthModuleProvider {
|
||||
authIdentity =
|
||||
await this.authIdentityService_.retrieveByProviderAndEntityId(
|
||||
entity_id,
|
||||
GoogleProvider.PROVIDER
|
||||
this.provider
|
||||
)
|
||||
} catch (error) {
|
||||
if (error.type === MedusaError.Types.NOT_FOUND) {
|
||||
const [createdAuthIdentity] = await this.authIdentityService_.create([
|
||||
{
|
||||
entity_id,
|
||||
provider: GoogleProvider.PROVIDER,
|
||||
provider: this.provider,
|
||||
user_metadata: jwtData!.payload,
|
||||
scope: this.scope_,
|
||||
},
|
||||
])
|
||||
authIdentity = createdAuthIdentity
|
||||
@@ -132,7 +130,7 @@ class GoogleProvider extends AbstractAuthModuleProvider {
|
||||
accessToken.token.id_token
|
||||
)
|
||||
|
||||
const { successRedirectUrl } = this.getConfigFromScope()
|
||||
const { successRedirectUrl } = this.getConfig()
|
||||
|
||||
return {
|
||||
success,
|
||||
@@ -144,8 +142,10 @@ class GoogleProvider extends AbstractAuthModuleProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private getConfigFromScope(): ProviderConfig {
|
||||
const config: Partial<ProviderConfig> = { ...this.scopeConfig_ }
|
||||
private getConfig(): ProviderConfig {
|
||||
// TODO: Fetch this from provider config
|
||||
// const config: Partial<ProviderConfig> = { ...this.scopeConfig_ }
|
||||
const config = {} as any
|
||||
|
||||
if (!config.clientID) {
|
||||
throw new Error("Google clientID is required")
|
||||
@@ -163,7 +163,7 @@ class GoogleProvider extends AbstractAuthModuleProvider {
|
||||
}
|
||||
|
||||
private originalURL(req: AuthenticationInput) {
|
||||
const host = req.headers.host
|
||||
const host = req.headers?.host
|
||||
const protocol = req.protocol
|
||||
const path = req.url || ""
|
||||
|
||||
@@ -173,7 +173,7 @@ class GoogleProvider extends AbstractAuthModuleProvider {
|
||||
private async getProviderConfig(
|
||||
req: AuthenticationInput
|
||||
): Promise<ProviderConfig> {
|
||||
const config = this.getConfigFromScope()
|
||||
const config = this.getConfig()
|
||||
|
||||
const callbackURL = config.callbackURL
|
||||
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export { default as EmailPasswordProvider } from "./email-password"
|
||||
export { default as GoogleProvider } from "./google"
|
||||
export { default as GoogleProvider } from "./google"
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import {
|
||||
AuthTypes,
|
||||
Context,
|
||||
DAL,
|
||||
FindConfig,
|
||||
RepositoryService,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
} from "@medusajs/utils"
|
||||
import { AuthIdentity } from "@models"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
authIdentityRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class AuthIdentityService<
|
||||
TEntity extends AuthIdentity = AuthIdentity
|
||||
> extends ModulesSdkUtils.internalModuleServiceFactory<InjectedDependencies>(
|
||||
AuthIdentity
|
||||
)<TEntity> {
|
||||
protected readonly authIdentityRepository_: RepositoryService<TEntity>
|
||||
protected baseRepository_: DAL.RepositoryService
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
this.authIdentityRepository_ = container.authIdentityRepository
|
||||
this.baseRepository_ = container.baseRepository
|
||||
}
|
||||
|
||||
@InjectManager("authIdentityRepository_")
|
||||
async retrieveByProviderAndEntityId<
|
||||
TEntityMethod = AuthTypes.AuthIdentityDTO
|
||||
>(
|
||||
entityId: string,
|
||||
provider: string,
|
||||
config: FindConfig<TEntityMethod> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<AuthTypes.AuthIdentityDTO> {
|
||||
const queryConfig = ModulesSdkUtils.buildQuery<TEntity>(
|
||||
{ entity_id: entityId, provider },
|
||||
{ ...config, take: 1 }
|
||||
)
|
||||
const [result] = await this.authIdentityRepository_.find(
|
||||
queryConfig,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (!result) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`AuthIdentity with entity_id: "${entityId}" and provider: "${provider}" not found`
|
||||
)
|
||||
}
|
||||
|
||||
return await this.baseRepository_.serialize<AuthTypes.AuthIdentityDTO>(
|
||||
result
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
AuthenticationInput,
|
||||
AuthenticationResponse,
|
||||
AuthIdentityProviderService,
|
||||
AuthTypes,
|
||||
Context,
|
||||
DAL,
|
||||
@@ -14,16 +15,17 @@ import { AuthIdentity } from "@models"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
|
||||
import {
|
||||
AbstractAuthModuleProvider,
|
||||
InjectManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
} from "@medusajs/utils"
|
||||
import AuthProviderService from "./auth-provider"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
authIdentityService: ModulesSdkTypes.InternalModuleService<any>
|
||||
authProviderService: AuthProviderService
|
||||
}
|
||||
|
||||
const generateMethodForModels = [AuthIdentity]
|
||||
@@ -42,9 +44,14 @@ export default class AuthModuleService<
|
||||
{
|
||||
protected baseRepository_: DAL.RepositoryService
|
||||
protected authIdentityService_: ModulesSdkTypes.InternalModuleService<TAuthIdentity>
|
||||
protected readonly authProviderService_: AuthProviderService
|
||||
|
||||
constructor(
|
||||
{ authIdentityService, baseRepository }: InjectedDependencies,
|
||||
{
|
||||
authIdentityService,
|
||||
authProviderService,
|
||||
baseRepository,
|
||||
}: InjectedDependencies,
|
||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
// @ts-ignore
|
||||
@@ -52,6 +59,7 @@ export default class AuthModuleService<
|
||||
|
||||
this.baseRepository_ = baseRepository
|
||||
this.authIdentityService_ = authIdentityService
|
||||
this.authProviderService_ = authProviderService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
@@ -116,34 +124,16 @@ export default class AuthModuleService<
|
||||
return Array.isArray(data) ? serializedUsers : serializedUsers[0]
|
||||
}
|
||||
|
||||
protected getRegisteredAuthenticationProvider(
|
||||
provider: string,
|
||||
{ authScope }: AuthenticationInput
|
||||
): AbstractAuthModuleProvider {
|
||||
let containerProvider: AbstractAuthModuleProvider
|
||||
try {
|
||||
containerProvider = this.__container__[`auth_provider_${provider}`]
|
||||
} catch (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`AuthenticationProvider: ${provider} wasn't registered in the module. Have you configured your options correctly?`
|
||||
)
|
||||
}
|
||||
|
||||
return containerProvider.withScope(authScope)
|
||||
}
|
||||
|
||||
async authenticate(
|
||||
provider: string,
|
||||
authenticationData: AuthenticationInput
|
||||
): Promise<AuthenticationResponse> {
|
||||
try {
|
||||
const registeredProvider = this.getRegisteredAuthenticationProvider(
|
||||
return await this.authProviderService_.authenticate(
|
||||
provider,
|
||||
authenticationData
|
||||
authenticationData,
|
||||
this.getAuthIdentityProviderService()
|
||||
)
|
||||
|
||||
return await registeredProvider.authenticate(authenticationData)
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
@@ -154,14 +144,49 @@ export default class AuthModuleService<
|
||||
authenticationData: AuthenticationInput
|
||||
): Promise<AuthenticationResponse> {
|
||||
try {
|
||||
const registeredProvider = this.getRegisteredAuthenticationProvider(
|
||||
return await this.authProviderService_.validateCallback(
|
||||
provider,
|
||||
authenticationData
|
||||
authenticationData,
|
||||
this.getAuthIdentityProviderService()
|
||||
)
|
||||
|
||||
return await registeredProvider.validateCallback(authenticationData)
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
getAuthIdentityProviderService(): AuthIdentityProviderService {
|
||||
return {
|
||||
retrieve: async ({ entity_id, provider }) => {
|
||||
const authIdentities = await this.authIdentityService_.list({
|
||||
entity_id,
|
||||
provider,
|
||||
})
|
||||
|
||||
if (!authIdentities.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`AuthIdentity with entity_id "${entity_id}" not found`
|
||||
)
|
||||
}
|
||||
|
||||
if (authIdentities.length > 1) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Multiple authIdentities found for entity_id "${entity_id}"`
|
||||
)
|
||||
}
|
||||
|
||||
return await this.baseRepository_.serialize<AuthTypes.AuthIdentityDTO>(
|
||||
authIdentities[0]
|
||||
)
|
||||
},
|
||||
create: async (data: AuthTypes.CreateAuthIdentityDTO) => {
|
||||
const createdAuthIdentity = await this.authIdentityService_.create(data)
|
||||
|
||||
return await this.baseRepository_.serialize<AuthTypes.AuthIdentityDTO>(
|
||||
createdAuthIdentity
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
56
packages/modules/auth/src/services/auth-provider.ts
Normal file
56
packages/modules/auth/src/services/auth-provider.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
AuthTypes,
|
||||
AuthenticationInput,
|
||||
AuthIdentityProviderService,
|
||||
AuthenticationResponse,
|
||||
} from "@medusajs/types"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { AuthProviderRegistrationPrefix } from "@types"
|
||||
|
||||
type InjectedDependencies = {
|
||||
[
|
||||
key: `${typeof AuthProviderRegistrationPrefix}${string}`
|
||||
]: AuthTypes.IAuthProvider
|
||||
}
|
||||
|
||||
export default class AuthProviderService {
|
||||
protected dependencies: InjectedDependencies
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
this.dependencies = container
|
||||
}
|
||||
|
||||
protected retrieveProviderRegistration(
|
||||
providerId: string
|
||||
): AuthTypes.IAuthProvider {
|
||||
try {
|
||||
return this.dependencies[`${AuthProviderRegistrationPrefix}${providerId}`]
|
||||
} catch (err) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Could not find a auth provider with id: ${providerId}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async authenticate(
|
||||
provider: string,
|
||||
auth: AuthenticationInput,
|
||||
authIdentityProviderService: AuthIdentityProviderService
|
||||
): Promise<AuthenticationResponse> {
|
||||
const providerHandler = this.retrieveProviderRegistration(provider)
|
||||
return await providerHandler.authenticate(auth, authIdentityProviderService)
|
||||
}
|
||||
|
||||
async validateCallback(
|
||||
provider: string,
|
||||
auth: AuthenticationInput,
|
||||
authIdentityProviderService: AuthIdentityProviderService
|
||||
): Promise<AuthenticationResponse> {
|
||||
const providerHandler = this.retrieveProviderRegistration(provider)
|
||||
return await providerHandler.validateCallback(
|
||||
auth,
|
||||
authIdentityProviderService
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default as AuthModuleService } from "./auth-module"
|
||||
export { default as AuthIdentityService } from "./auth-identity"
|
||||
export { default as AuthProviderService } from "./auth-provider"
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
import { ModuleProviderExports } from "@medusajs/types"
|
||||
import { ModuleServiceInitializeOptions } from "@medusajs/types"
|
||||
import { Logger } from "@medusajs/types"
|
||||
|
||||
export type InitializeModuleInjectableDependencies = {
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
export const AuthIdentifiersRegistrationName = "auth_providers_identifier"
|
||||
|
||||
export const AuthProviderRegistrationPrefix = "au_"
|
||||
|
||||
export type AuthModuleOptions = Partial<ModuleServiceInitializeOptions> & {
|
||||
/**
|
||||
* Providers to be registered
|
||||
*/
|
||||
providers?: {
|
||||
/**
|
||||
* The module provider to be registered
|
||||
*/
|
||||
resolve: string | ModuleProviderExports
|
||||
options: {
|
||||
/**
|
||||
* key value pair of the provider name and the configuration to be passed to the provider constructor
|
||||
*/
|
||||
config: Record<string, unknown>
|
||||
}
|
||||
}[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user