diff --git a/packages/medusa-plugin-auth/package.json b/packages/medusa-plugin-auth/package.json index 6f41c354..b9580c0f 100644 --- a/packages/medusa-plugin-auth/package.json +++ b/packages/medusa-plugin-auth/package.json @@ -54,6 +54,11 @@ "@medusajs/medusa": ">=1.17.x", "@types/express": "^4.17.17", "@types/jest": "^29.1.2", + "@types/passport-auth0": "^1.0.9", + "@types/passport-azure-ad": "^4.3.5", + "@types/passport-facebook": "^3.0.3", + "@types/passport-google-oauth2": "^0.1.8", + "@types/passport-linkedin-oauth2": "^1.5.6", "@types/passport-oauth2": "^1.4.15", "jest": "^29.1.2", "passport": "^0.6.0", diff --git a/packages/medusa-plugin-auth/src/api/index.ts b/packages/medusa-plugin-auth/src/api/index.ts index a4f11653..dd9d8910 100644 --- a/packages/medusa-plugin-auth/src/api/index.ts +++ b/packages/medusa-plugin-auth/src/api/index.ts @@ -9,23 +9,43 @@ import FirebaseStrategy from '../auth-strategies/firebase'; import Auth0Strategy from '../auth-strategies/auth0'; import AzureStrategy from '../auth-strategies/azure-oidc'; -import { AuthOptions } from '../types'; +import { AuthOptions, AuthOptionsWrapper, handleOption } from '../types'; -export default function (rootDirectory, pluginOptions: AuthOptions): Router[] { - const configModule = loadConfig(rootDirectory) as ConfigModule; +export default async function(rootDirectory, pluginOptions: AuthOptions[]): Promise { + const configModule = loadConfig(rootDirectory); return loadRouters(configModule, pluginOptions); } -function loadRouters(configModule: ConfigModule, options: AuthOptions): Router[] { +async function loadRouters(configModule: ConfigModule, options: AuthOptionsWrapper[]): Promise { const routers: Router[] = []; - routers.push(...OAuth2Strategy.getRouter(configModule, options)); - routers.push(...GoogleStrategy.getRouter(configModule, options)); - routers.push(...FacebookStrategy.getRouter(configModule, options)); - routers.push(...LinkedinStrategy.getRouter(configModule, options)); - routers.push(...FirebaseStrategy.getRouter(configModule, options)); - routers.push(...Auth0Strategy.getRouter(configModule, options)); - routers.push(...AzureStrategy.getRouter(configModule, options)); + for (const opt of options) { + const option = await handleOption(opt, configModule); + + switch (option.type) { + case 'azure_oidc': + routers.push(...AzureStrategy.getRouter(configModule, option)); + break; + case 'google': + routers.push(...GoogleStrategy.getRouter(configModule, option)); + break; + case 'facebook': + routers.push(...FacebookStrategy.getRouter(configModule, option)); + break; + case 'linkedin': + routers.push(...LinkedinStrategy.getRouter(configModule, option)); + break; + case 'firebase': + routers.push(...FirebaseStrategy.getRouter(configModule, option)); + break; + case 'auth0': + routers.push(...Auth0Strategy.getRouter(configModule, option)); + break; + case 'oauth2': + routers.push(...OAuth2Strategy.getRouter(configModule, option)); + break; + } + } return routers; } diff --git a/packages/medusa-plugin-auth/src/auth-strategies/auth0/__tests__/admin/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/auth0/__tests__/admin/verify-callback.spec.ts index cd389d81..6ef6908d 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/auth0/__tests__/admin/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/auth0/__tests__/admin/verify-callback.spec.ts @@ -1,9 +1,10 @@ import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { Auth0AdminStrategy } from '../../admin'; -import { AUTH_PROVIDER_KEY } from '../../../../types'; -import { Auth0Options, AUTH0_ADMIN_STRATEGY_NAME, Profile, ExtraParams } from '../../types'; +import { AUTH_PROVIDER_KEY, IStrategy } from '../../../../types'; +import { AUTH0_ADMIN_STRATEGY_NAME, Auth0Options, ExtraParams } from '../../types'; +import { Profile } from 'passport-auth0'; +import { getAuth0AdminStrategy } from '../../admin'; -describe('Auth0 admin strategy verify callback', function () { +describe('Auth0 admin strategy verify callback', function() { const existsEmail = 'exists@test.fr'; const existsEmailWithProviderKey = 'exist3s@test.fr'; const existsEmailWithWrongProviderKey = 'exist4s@test.fr'; @@ -12,9 +13,9 @@ describe('Auth0 admin strategy verify callback', function () { let req: Request; let accessToken: string; let refreshToken: string; - let profile: Profile; + let profile: Partial; let extraParams: ExtraParams; - let auth0AdminStrategy: Auth0AdminStrategy; + let auth0AdminStrategy: IStrategy; beforeEach(() => { profile = { @@ -38,7 +39,7 @@ describe('Auth0 admin strategy verify callback', function () { return { id: 'test2', metadata: { - [AUTH_PROVIDER_KEY]: AUTH0_ADMIN_STRATEGY_NAME, + [AUTH_PROVIDER_KEY]: AUTH0_ADMIN_STRATEGY_NAME + '_test', }, }; } @@ -62,8 +63,9 @@ describe('Auth0 admin strategy verify callback', function () { } as MedusaContainer; }); - describe('when strict is set to admin', function () { + describe('when strict is set to admin', function() { beforeEach(() => { + const Auth0AdminStrategy = getAuth0AdminStrategy('test'); auth0AdminStrategy = new Auth0AdminStrategy( container, {} as ConfigModule, @@ -73,7 +75,7 @@ describe('Auth0 admin strategy verify callback', function () { clientSecret: 'fake', admin: { callbackUrl: '/fakeCallbackUrl' }, } as Auth0Options, - 'admin' + 'admin', ); }); @@ -90,7 +92,7 @@ describe('Auth0 admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); }); @@ -128,8 +130,9 @@ describe('Auth0 admin strategy verify callback', function () { }); }); - describe('when strict is set for store only', function () { + describe('when strict is set for store only', function() { beforeEach(() => { + const Auth0AdminStrategy = getAuth0AdminStrategy('test'); auth0AdminStrategy = new Auth0AdminStrategy( container, {} as ConfigModule, @@ -139,7 +142,7 @@ describe('Auth0 admin strategy verify callback', function () { clientSecret: 'fake', admin: { callbackUrl: '/fakeCallbackUrl' }, } as Auth0Options, - 'store' + 'store', ); }); @@ -156,7 +159,7 @@ describe('Auth0 admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); }); diff --git a/packages/medusa-plugin-auth/src/auth-strategies/auth0/__tests__/store/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/auth0/__tests__/store/verify-callback.spec.ts index 0fd852e2..66f30d72 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/auth0/__tests__/store/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/auth0/__tests__/store/verify-callback.spec.ts @@ -1,9 +1,10 @@ -import { Auth0StoreStrategy } from '../../store'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AUTH_PROVIDER_KEY, CUSTOMER_METADATA_KEY } from '../../../../types'; -import { Auth0Options, AUTH0_STORE_STRATEGY_NAME, Profile, ExtraParams } from '../../types'; +import { AUTH_PROVIDER_KEY, CUSTOMER_METADATA_KEY, IStrategy } from '../../../../types'; +import { AUTH0_STORE_STRATEGY_NAME, Auth0Options, ExtraParams } from '../../types'; +import { Profile } from 'passport-auth0'; +import { getAuth0StoreStrategy } from '../../store'; -describe('Auth0 store strategy verify callback', function () { +describe('Auth0 store strategy verify callback', function() { const existsEmail = 'exists@test.fr'; const existsEmailWithMeta = 'exist2s@test.fr'; const existsEmailWithMetaAndProviderKey = 'exist3s@test.fr'; @@ -13,9 +14,9 @@ describe('Auth0 store strategy verify callback', function () { let req: Request; let accessToken: string; let refreshToken: string; - let profile: Profile; + let profile: Partial; let extraParams: ExtraParams; - let auth0StoreStrategy: Auth0StoreStrategy; + let auth0StoreStrategy: IStrategy; let updateFn; let createFn; @@ -36,12 +37,12 @@ describe('Auth0 store strategy verify callback', function () { resolve: (name: string): T => { const container_ = { manager: { - transaction: function (cb) { + transaction: function(cb) { return cb(); }, }, customerService: { - withTransaction: function () { + withTransaction: function() { return this; }, create: createFn, @@ -67,7 +68,7 @@ describe('Auth0 store strategy verify callback', function () { id: 'test3', metadata: { [CUSTOMER_METADATA_KEY]: true, - [AUTH_PROVIDER_KEY]: AUTH0_STORE_STRATEGY_NAME, + [AUTH_PROVIDER_KEY]: AUTH0_STORE_STRATEGY_NAME + '_test', }, }; } @@ -92,8 +93,9 @@ describe('Auth0 store strategy verify callback', function () { } as MedusaContainer; }); - describe('when strict is set to store', function () { + describe('when strict is set to store', function() { beforeEach(() => { + const Auth0StoreStrategy = getAuth0StoreStrategy('test'); auth0StoreStrategy = new Auth0StoreStrategy( container, {} as ConfigModule, @@ -103,7 +105,7 @@ describe('Auth0 store strategy verify callback', function () { clientSecret: 'fake', store: { callbackUrl: '/fakeCallbackUrl' }, } as Auth0Options, - 'store' + 'store', ); }); @@ -120,7 +122,7 @@ describe('Auth0 store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); @@ -144,7 +146,7 @@ describe('Auth0 store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); expect(updateFn).toHaveBeenCalledTimes(1); }); @@ -158,7 +160,7 @@ describe('Auth0 store strategy verify callback', function () { .validate(req, accessToken, refreshToken, extraParams, profile) .catch((err) => err); expect(err).toEqual( - new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`) + new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`), ); }); @@ -175,14 +177,15 @@ describe('Auth0 store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); expect(createFn).toHaveBeenCalledTimes(1); }); }); - describe('when strict is set to admin', function () { + describe('when strict is set to admin', function() { beforeEach(() => { + const Auth0StoreStrategy = getAuth0StoreStrategy('test'); auth0StoreStrategy = new Auth0StoreStrategy( container, {} as ConfigModule, @@ -192,7 +195,7 @@ describe('Auth0 store strategy verify callback', function () { clientSecret: 'fake', store: { callbackUrl: '/fakeCallbackUrl' }, } as Auth0Options, - 'admin' + 'admin', ); }); @@ -209,7 +212,7 @@ describe('Auth0 store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); @@ -222,7 +225,7 @@ describe('Auth0 store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); }); @@ -235,7 +238,7 @@ describe('Auth0 store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); expect(updateFn).toHaveBeenCalledTimes(1); }); @@ -249,7 +252,7 @@ describe('Auth0 store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test4', - }) + }), ); }); @@ -266,7 +269,7 @@ describe('Auth0 store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); expect(createFn).toHaveBeenCalledTimes(1); }); diff --git a/packages/medusa-plugin-auth/src/auth-strategies/auth0/admin.ts b/packages/medusa-plugin-auth/src/auth-strategies/auth0/admin.ts index 074bae3a..d9c08faa 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/auth0/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/auth0/admin.ts @@ -1,70 +1,76 @@ -import { Strategy as Auth0Strategy } from 'passport-auth0'; +import { Profile, Strategy as Auth0Strategy, StrategyOptionWithRequest } from 'passport-auth0'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; import { Router } from 'express'; -import { AUTH0_ADMIN_STRATEGY_NAME, Auth0Options, Profile, ExtraParams } from './types'; +import { AUTH0_ADMIN_STRATEGY_NAME, Auth0Options, ExtraParams } from './types'; import { PassportStrategy } from '../../core/passport/Strategy'; import { validateAdminCallback } from '../../core/validate-callback'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class Auth0AdminStrategy extends PassportStrategy(Auth0Strategy, AUTH0_ADMIN_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: Auth0Options, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - domain: strategyOptions.auth0Domain, - clientID: strategyOptions.clientID, - clientSecret: strategyOptions.clientSecret, - callbackURL: strategyOptions.admin.callbackUrl, - passReqToCallback: true, - state: true, - }); - } +export function getAuth0AdminStrategy(id: string): StrategyFactory { + const strategyName = `${AUTH0_ADMIN_STRATEGY_NAME}_${id}`; + return class extends PassportStrategy(Auth0Strategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: Auth0Options, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + domain: strategyOptions.auth0Domain, + clientID: strategyOptions.clientID, + clientSecret: strategyOptions.clientSecret, + callbackURL: strategyOptions.admin.callbackUrl, + passReqToCallback: true, + state: true, + } as StrategyOptionWithRequest); + } - async validate( - req: Request, - accessToken: string, - refreshToken: string, - extraParams: ExtraParams, - profile: Profile - ): Promise { - if (this.strategyOptions.admin.verifyCallback) { - const validateRes = await this.strategyOptions.admin.verifyCallback( - this.container, - req, - accessToken, - refreshToken, - extraParams, - profile, - this.strict - ); + async validate( + req: Request, + accessToken: string, + refreshToken: string, + extraParams: ExtraParams, + profile: Profile, + ): Promise { + if (this.strategyOptions.admin.verifyCallback) { + const validateRes = await this.strategyOptions.admin.verifyCallback( + this.container, + req, + accessToken, + refreshToken, + extraParams, + profile, + this.strict, + ); + return { + ...validateRes, + accessToken, + }; + } + const validateRes = await validateAdminCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'auth0', + strict: this.strict, + strategyName, + }); return { ...validateRes, accessToken, }; } - const validateRes = await validateAdminCallback(profile, { - container: this.container, - strategyErrorIdentifier: 'auth0', - strict: this.strict, - }); - return { - ...validateRes, - accessToken, - }; - } + }; } /** * Return the router that holds the auth0 admin authentication routes + * @param id * @param auth0 * @param configModule */ -export function getAuth0AdminAuthRouter(auth0: Auth0Options, configModule: ConfigModule): Router { +export function getAuth0AdminAuthRouter(id: string, auth0: Auth0Options, configModule: ConfigModule): Router { + const strategyName = `${AUTH0_ADMIN_STRATEGY_NAME}_${id}`; return passportAuthRoutesBuilder({ domain: 'admin', configModule, diff --git a/packages/medusa-plugin-auth/src/auth-strategies/auth0/index.ts b/packages/medusa-plugin-auth/src/auth-strategies/auth0/index.ts index e7d9bad5..4ffb9f2b 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/auth0/index.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/auth0/index.ts @@ -1,34 +1,38 @@ -import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AuthOptions, StrategyExport } from '../../types'; +import { StrategyExport } from '../../types'; import { Router } from 'express'; -import { getAuth0AdminAuthRouter, Auth0AdminStrategy } from './admin'; -import { getAuth0StoreAuthRouter, Auth0StoreStrategy } from './store'; +import { getAuth0AdminAuthRouter, getAuth0AdminStrategy } from './admin'; +import { getAuth0StoreAuthRouter, getAuth0StoreStrategy } from './store'; +import { Auth0Options } from './types'; export * from './admin'; export * from './store'; export * from './types'; export default { - load: (container: MedusaContainer, configModule: ConfigModule, options: AuthOptions): void => { - if (options.auth0?.admin) { - new Auth0AdminStrategy(container, configModule, options.auth0, options.strict); + load: (container, configModule, option): void => { + const id = option.identifier ?? option.type; + if (option.admin) { + const Clazz = getAuth0AdminStrategy(id); + new Clazz(container, configModule, option, option.strict); } - if (options.auth0?.store) { - new Auth0StoreStrategy(container, configModule, options.auth0, options.strict); + if (option.store) { + const Clazz = getAuth0StoreStrategy(id); + new Clazz(container, configModule, option, option.strict); } }, - getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { + getRouter: (configModule, option): Router[] => { const routers = []; + const id = option.identifier ?? option.type; - if (options.auth0?.admin) { - routers.push(getAuth0AdminAuthRouter(options.auth0, configModule)); + if (option.admin) { + routers.push(getAuth0AdminAuthRouter(id, option, configModule)); } - if (options.auth0?.store) { - routers.push(getAuth0StoreAuthRouter(options.auth0, configModule)); + if (option.store) { + routers.push(getAuth0StoreAuthRouter(id, option, configModule)); } return routers; }, -} as StrategyExport; +} as StrategyExport; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/auth0/store.ts b/packages/medusa-plugin-auth/src/auth-strategies/auth0/store.ts index 9a6200f3..8bf1b29e 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/auth0/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/auth0/store.ts @@ -1,78 +1,84 @@ import { Router } from 'express'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { Strategy as Auth0Strategy } from 'passport-auth0'; -import { Auth0Options, Profile, ExtraParams, AUTH0_STORE_STRATEGY_NAME } from './types'; +import { Profile, Strategy as Auth0Strategy, StrategyOptionWithRequest } from 'passport-auth0'; +import { AUTH0_STORE_STRATEGY_NAME, Auth0Options, ExtraParams } from './types'; import { PassportStrategy } from '../../core/passport/Strategy'; import { validateStoreCallback } from '../../core/validate-callback'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class Auth0StoreStrategy extends PassportStrategy(Auth0Strategy, AUTH0_STORE_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: Auth0Options, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - domain: strategyOptions.auth0Domain, - clientID: strategyOptions.clientID, - clientSecret: strategyOptions.clientSecret, - callbackURL: strategyOptions.store.callbackUrl, - passReqToCallback: true, - state: true, - }); - } +export function getAuth0StoreStrategy(id: string): StrategyFactory { + const strategyName = `${AUTH0_STORE_STRATEGY_NAME}_${id}`; + return class extends PassportStrategy(Auth0Strategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: Auth0Options, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + domain: strategyOptions.auth0Domain, + clientID: strategyOptions.clientID, + clientSecret: strategyOptions.clientSecret, + callbackURL: strategyOptions.store.callbackUrl, + passReqToCallback: true, + state: true, + } as StrategyOptionWithRequest); + } - async validate( - req: Request, - accessToken: string, - refreshToken: string, - extraParams: ExtraParams, - profile: Profile - ): Promise { - if (this.strategyOptions.store.verifyCallback) { - const validateRes = await this.strategyOptions.store.verifyCallback( - this.container, - req, - accessToken, - refreshToken, - extraParams, - profile, - this.strict - ); + async validate( + req: Request, + accessToken: string, + refreshToken: string, + extraParams: ExtraParams, + profile: Profile, + ): Promise { + if (this.strategyOptions.store.verifyCallback) { + const validateRes = await this.strategyOptions.store.verifyCallback( + this.container, + req, + accessToken, + refreshToken, + extraParams, + profile, + this.strict, + ); + return { + ...validateRes, + accessToken, + }; + } + + const validateRes = await validateStoreCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'auth0', + strict: this.strict, + strategyName, + }); return { ...validateRes, accessToken, }; } - - const validateRes = await validateStoreCallback(profile, { - container: this.container, - strategyErrorIdentifier: 'auth0', - strict: this.strict, - }); - return { - ...validateRes, - accessToken, - }; - } + }; } /** * Return the router that holds the auth0 store authentication routes + * @param id * @param auth0 * @param configModule */ -export function getAuth0StoreAuthRouter(auth0: Auth0Options, configModule: ConfigModule): Router { +export function getAuth0StoreAuthRouter(id: string, auth0: Auth0Options, configModule: ConfigModule): Router { + const strategyName = `${AUTH0_STORE_STRATEGY_NAME}_${id}`; return passportAuthRoutesBuilder({ domain: 'store', configModule, authPath: auth0.store.authPath ?? '/store/auth/auth0', authCallbackPath: auth0.store.authCallbackPath ?? '/store/auth/auth0/cb', successRedirect: auth0.store.successRedirect, - strategyName: AUTH0_STORE_STRATEGY_NAME, + strategyName, passportAuthenticateMiddlewareOptions: { scope: 'openid email profile', }, diff --git a/packages/medusa-plugin-auth/src/auth-strategies/auth0/types.ts b/packages/medusa-plugin-auth/src/auth-strategies/auth0/types.ts index dc573f39..82f635f6 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/auth0/types.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/auth0/types.ts @@ -1,10 +1,10 @@ import { MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AuthOptions } from '../../types'; +import { AuthProvider } from '../../types'; +import { Profile } from 'passport-auth0'; export const AUTH0_ADMIN_STRATEGY_NAME = 'auth0.admin.medusa-auth-plugin'; export const AUTH0_STORE_STRATEGY_NAME = 'auth0.store.medusa-auth-plugin'; -export type Profile = { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }; export type ExtraParams = { audience?: string | undefined; connection?: string | undefined; @@ -12,6 +12,7 @@ export type ExtraParams = { }; export type Auth0Options = { + type: 'auth0'; clientID: string; clientSecret: string; auth0Domain: string; @@ -37,7 +38,7 @@ export type Auth0Options = { refreshToken: string, extraParams: ExtraParams, profile: Profile, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; @@ -64,7 +65,7 @@ export type Auth0Options = { refreshToken: string, extraParams: ExtraParams, profile: Profile, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/__tests__/admin/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/__tests__/admin/verify-callback.spec.ts index 7e4472eb..83a98329 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/__tests__/admin/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/__tests__/admin/verify-callback.spec.ts @@ -1,9 +1,9 @@ import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AzureAdminStrategy } from '../../admin'; -import { AUTH_PROVIDER_KEY } from '../../../../types'; -import { AzureAuthOptions, AZURE_ADMIN_STRATEGY_NAME } from '../../types'; +import { AUTH_PROVIDER_KEY, IStrategy } from '../../../../types'; +import { AZURE_ADMIN_STRATEGY_NAME, AzureAuthOptions } from '../../types'; +import { getAzureAdminStrategy } from '../../admin'; -describe('Azure AD admin strategy verify callback', function () { +describe('Azure AD admin strategy verify callback', function() { const existsEmail = 'exists@test.fr'; const existsEmailWithProviderKey = 'exist3s@test.fr'; const existsEmailWithWrongProviderKey = 'exist4s@test.fr'; @@ -11,7 +11,7 @@ describe('Azure AD admin strategy verify callback', function () { let container: MedusaContainer; let req: Request; let profile: { upn: string; name?: { givenName?: string; familyName?: string } }; - let azureAdminStrategy: AzureAdminStrategy; + let azureAdminStrategy: IStrategy; beforeEach(() => { profile = { @@ -33,7 +33,7 @@ describe('Azure AD admin strategy verify callback', function () { return { id: 'test2', metadata: { - [AUTH_PROVIDER_KEY]: AZURE_ADMIN_STRATEGY_NAME, + [AUTH_PROVIDER_KEY]: AZURE_ADMIN_STRATEGY_NAME + '_test', }, }; } @@ -57,8 +57,9 @@ describe('Azure AD admin strategy verify callback', function () { } as MedusaContainer; }); - describe('when strict is set to admin', function () { + describe('when strict is set to admin', function() { beforeEach(() => { + const AzureAdminStrategy = getAzureAdminStrategy('test'); azureAdminStrategy = new AzureAdminStrategy( container, {} as ConfigModule, @@ -73,7 +74,7 @@ describe('Azure AD admin strategy verify callback', function () { allowHttpForRedirectUrl: true, }, } as AzureAuthOptions, - 'admin' + 'admin', ); }); @@ -90,7 +91,7 @@ describe('Azure AD admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); }); @@ -122,8 +123,9 @@ describe('Azure AD admin strategy verify callback', function () { }); }); - describe('when strict is set to store', function () { + describe('when strict is set to store', function() { beforeEach(() => { + const AzureAdminStrategy = getAzureAdminStrategy('test'); azureAdminStrategy = new AzureAdminStrategy( container, {} as ConfigModule, @@ -138,7 +140,7 @@ describe('Azure AD admin strategy verify callback', function () { allowHttpForRedirectUrl: true, }, } as AzureAuthOptions, - 'store' + 'store', ); }); @@ -155,7 +157,7 @@ describe('Azure AD admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); }); @@ -168,7 +170,7 @@ describe('Azure AD admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); }); @@ -181,7 +183,7 @@ describe('Azure AD admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); diff --git a/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/__tests__/store/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/__tests__/store/verify-callback.spec.ts index 30d4e8a5..15acba07 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/__tests__/store/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/__tests__/store/verify-callback.spec.ts @@ -1,9 +1,9 @@ import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AzureStoreStrategy } from '../../store'; -import { AUTH_PROVIDER_KEY, CUSTOMER_METADATA_KEY } from '../../../../types'; -import { AzureAuthOptions, AZURE_STORE_STRATEGY_NAME } from '../../types'; +import { getAzureStoreStrategy } from '../../store'; +import { AUTH_PROVIDER_KEY, CUSTOMER_METADATA_KEY, IStrategy } from '../../../../types'; +import { AZURE_STORE_STRATEGY_NAME, AzureAuthOptions } from '../../types'; -describe('Google store strategy verify callback', function () { +describe('Google store strategy verify callback', function() { const existsEmail = 'exists@test.fr'; const existsEmailWithMeta = 'exist2s@test.fr'; const existsEmailWithMetaAndProviderKey = 'exist3s@test.fr'; @@ -12,7 +12,7 @@ describe('Google store strategy verify callback', function () { let container: MedusaContainer; let req: Request; let profile: { upn: string; name?: { givenName?: string; familyName?: string } }; - let azureStoreStrategy: AzureStoreStrategy; + let azureStoreStrategy: IStrategy; let updateFn; let createFn; @@ -32,12 +32,12 @@ describe('Google store strategy verify callback', function () { resolve: (name: string): T => { const container_ = { manager: { - transaction: function (cb) { + transaction: function(cb) { return cb(); }, }, customerService: { - withTransaction: function () { + withTransaction: function() { return this; }, update: updateFn, @@ -63,7 +63,7 @@ describe('Google store strategy verify callback', function () { id: 'test3', metadata: { [CUSTOMER_METADATA_KEY]: true, - [AUTH_PROVIDER_KEY]: AZURE_STORE_STRATEGY_NAME, + [AUTH_PROVIDER_KEY]: AZURE_STORE_STRATEGY_NAME + '_test', }, }; } @@ -88,9 +88,10 @@ describe('Google store strategy verify callback', function () { } as MedusaContainer; }); - describe('when strict is set to store', function () { + describe('when strict is set to store', function() { beforeEach(() => { - azureStoreStrategy = new AzureStoreStrategy( + const Class = getAzureStoreStrategy('test'); + azureStoreStrategy = new Class( container, {} as ConfigModule, { @@ -104,7 +105,7 @@ describe('Google store strategy verify callback', function () { allowHttpForRedirectUrl: true, }, } as AzureAuthOptions, - 'store' + 'store', ); }); @@ -121,7 +122,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); @@ -143,7 +144,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); expect(updateFn).toHaveBeenCalledTimes(1); }); @@ -155,7 +156,7 @@ describe('Google store strategy verify callback', function () { const err = await azureStoreStrategy.validate(req, profile).catch((err) => err); expect(err).toEqual( - new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`) + new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`), ); }); @@ -172,15 +173,16 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); expect(createFn).toHaveBeenCalledTimes(1); }); }); - describe('when strict is set to admin', function () { + describe('when strict is set to admin', function() { beforeEach(() => { - azureStoreStrategy = new AzureStoreStrategy( + const Class = getAzureStoreStrategy('test'); + azureStoreStrategy = new Class( container, {} as ConfigModule, { @@ -194,7 +196,7 @@ describe('Google store strategy verify callback', function () { allowHttpForRedirectUrl: true, }, } as AzureAuthOptions, - 'admin' + 'admin', ); }); @@ -211,7 +213,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); @@ -224,7 +226,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); }); @@ -237,7 +239,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); expect(updateFn).toHaveBeenCalledTimes(1); }); @@ -251,7 +253,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test4', - }) + }), ); }); @@ -268,7 +270,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); expect(createFn).toHaveBeenCalledTimes(1); }); diff --git a/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/admin.ts b/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/admin.ts index 8df2f38e..3ce841d5 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/admin.ts @@ -1,65 +1,75 @@ -import { OIDCStrategy as AzureStrategy } from 'passport-azure-ad'; +import { IOIDCStrategyOptionWithRequest, IProfile, OIDCStrategy as AzureStrategy } from 'passport-azure-ad'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; import { Router } from 'express'; -import { AZURE_ADMIN_STRATEGY_NAME, AzureAuthOptions, Profile, ResponseType, ResponseMode } from './types'; +import { AZURE_ADMIN_STRATEGY_NAME, AzureAuthOptions, Profile, ResponseMode, ResponseType } from './types'; import { PassportStrategy } from '../../core/passport/Strategy'; import { validateAdminCallback } from '../../core/validate-callback'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class AzureAdminStrategy extends PassportStrategy(AzureStrategy, AZURE_ADMIN_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: AzureAuthOptions, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - identityMetadata: strategyOptions.admin.identityMetadata, - clientID: strategyOptions.admin.clientID, - clientSecret: strategyOptions.admin.clientSecret, - responseType: strategyOptions.admin.responseType ?? ResponseType.Code, - responseMode: strategyOptions.admin.responseMode ?? ResponseMode.Query, - redirectUrl: strategyOptions.admin.callbackUrl, - allowHttpForRedirectUrl: strategyOptions.admin.allowHttpForRedirectUrl ?? false, - validateIssuer: strategyOptions.admin.validateIssuer ?? true, - isB2C: strategyOptions.admin.isB2C ?? false, - issuer: strategyOptions.admin.issuer, - passReqToCallback: true, - }); - } - - async validate(req: Request, profile: any): Promise { - if (this.strategyOptions.admin.verifyCallback) { - return await this.strategyOptions.admin.verifyCallback(this.container, req, profile, this.strict); +/** + * Return the azure admin strategy + * @param id + */ +export function getAzureAdminStrategy(id: string): StrategyFactory { + const strategyName = `${AZURE_ADMIN_STRATEGY_NAME}_${id}`; + return class extends PassportStrategy(AzureStrategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: AzureAuthOptions, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + identityMetadata: strategyOptions.admin.identityMetadata, + clientID: strategyOptions.admin.clientID, + clientSecret: strategyOptions.admin.clientSecret, + responseType: strategyOptions.admin.responseType ?? ResponseType.Code, + responseMode: strategyOptions.admin.responseMode ?? ResponseMode.Query, + redirectUrl: strategyOptions.admin.callbackUrl, + allowHttpForRedirectUrl: strategyOptions.admin.allowHttpForRedirectUrl ?? false, + validateIssuer: strategyOptions.admin.validateIssuer ?? true, + isB2C: strategyOptions.admin.isB2C ?? false, + issuer: strategyOptions.admin.issuer, + passReqToCallback: true, + } as IOIDCStrategyOptionWithRequest); } - const authprofile: Profile = { - emails: [{ value: profile?.upn }], - name: { givenName: profile?.name?.givenName, familyName: profile?.name?.familyName }, - }; + async validate(req: Request, profile: IProfile): Promise { + if (this.strategyOptions.admin.verifyCallback) { + return await this.strategyOptions.admin.verifyCallback(this.container, req, profile, this.strict); + } - return await validateAdminCallback(authprofile, { - container: this.container, - strategyErrorIdentifier: 'azure_oidc', - strict: this.strict, - }); - } + const authprofile: Profile = { + emails: [{ value: profile?.upn }], + name: { givenName: profile?.name?.givenName, familyName: profile?.name?.familyName }, + }; + + return await validateAdminCallback(authprofile, { + container: this.container, + strategyErrorIdentifier: 'azure_oidc', + strict: this.strict, + strategyName, + }); + } + }; } /** * Return the router that hold the azure admin authentication routes * @param azure * @param configModule + * @param id */ -export function getAzureAdminAuthRouter(azure: AzureAuthOptions, configModule: ConfigModule): Router { +export function getAzureAdminAuthRouter(id: string, azure: AzureAuthOptions, configModule: ConfigModule): Router { + const strategyName = `${AZURE_ADMIN_STRATEGY_NAME}_${id}`; return passportAuthRoutesBuilder({ domain: 'admin', configModule, authPath: azure.admin.authPath ?? '/admin/auth/azure', authCallbackPath: azure.admin.authCallbackPath ?? '/admin/auth/azure/cb', successRedirect: azure.admin.successRedirect, - strategyName: AZURE_ADMIN_STRATEGY_NAME, + strategyName, passportAuthenticateMiddlewareOptions: { scope: azure.admin.scope ?? [], }, diff --git a/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/index.ts b/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/index.ts index 3c0e9835..2b6630bb 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/index.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/index.ts @@ -1,34 +1,38 @@ -import { AuthOptions, StrategyExport } from '../../types'; +import { StrategyExport } from '../../types'; import { Router } from 'express'; -import { getAzureAdminAuthRouter, AzureAdminStrategy } from './admin'; -import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { getAzureStoreAuthRouter, AzureStoreStrategy } from './store'; +import { getAzureAdminAuthRouter, getAzureAdminStrategy } from './admin'; +import { getAzureStoreAuthRouter, getAzureStoreStrategy } from './store'; +import { AzureAuthOptions } from './types'; export * from './types'; export * from './admin'; export * from './store'; export default { - load: (container: MedusaContainer, configModule: ConfigModule, options: AuthOptions): void => { - if (options.azure_oidc?.admin) { - new AzureAdminStrategy(container, configModule, options.azure_oidc, options.strict); + load: (container, configModule, option): void => { + const id = option.identifier ?? option.type; + if (option?.admin) { + const Clazz = getAzureAdminStrategy(id); + new Clazz(container, configModule, option, option.strict); } - if (options.azure_oidc?.store) { - new AzureStoreStrategy(container, configModule, options.azure_oidc, options.strict); + if (option?.store) { + const Clazz = getAzureStoreStrategy(id); + new Clazz(container, configModule, option, option.strict); } }, - getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { - const routers = []; + getRouter: (configModule, option) => { + const id = option.identifier ?? option.type; + const routers: Router[] = []; - if (options.azure_oidc?.admin) { - routers.push(getAzureAdminAuthRouter(options.azure_oidc, configModule)); + if (option?.admin) { + routers.push(getAzureAdminAuthRouter(id, option, configModule)); } - if (options.azure_oidc?.store) { - routers.push(getAzureStoreAuthRouter(options.azure_oidc, configModule)); + if (option?.store) { + routers.push(getAzureStoreAuthRouter(id, option, configModule)); } return routers; }, -} as StrategyExport; +} as StrategyExport; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/store.ts b/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/store.ts index 3902c5c3..3a18a83f 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/store.ts @@ -1,65 +1,76 @@ import { Router } from 'express'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { OIDCStrategy as AzureStrategy } from 'passport-azure-ad'; +import { IOIDCStrategyOptionWithRequest, IProfile, OIDCStrategy as AzureStrategy } from 'passport-azure-ad'; import { PassportStrategy } from '../../core/passport/Strategy'; -import { AZURE_STORE_STRATEGY_NAME, AzureAuthOptions, Profile, ResponseType, ResponseMode } from './types'; +import { AZURE_STORE_STRATEGY_NAME, AzureAuthOptions, Profile, ResponseMode, ResponseType } from './types'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; import { validateStoreCallback } from '../../core/validate-callback'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class AzureStoreStrategy extends PassportStrategy(AzureStrategy, AZURE_STORE_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: AzureAuthOptions, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - identityMetadata: strategyOptions.store.identityMetadata, - clientID: strategyOptions.store.clientID, - clientSecret: strategyOptions.store.clientSecret, - responseType: strategyOptions.store.responseType ?? ResponseType.Code, - responseMode: strategyOptions.store.responseMode ?? ResponseMode.Query, - redirectUrl: strategyOptions.store.callbackUrl, - allowHttpForRedirectUrl: strategyOptions.store.allowHttpForRedirectUrl ?? false, - validateIssuer: strategyOptions.store.validateIssuer ?? true, - isB2C: strategyOptions.store.isB2C ?? false, - issuer: strategyOptions.store.issuer, - passReqToCallback: true, - }); - } +/** + * Return the azure store strategy + * @param id + */ +export function getAzureStoreStrategy(id: string): StrategyFactory { + const strategyName = `${AZURE_STORE_STRATEGY_NAME}_${id}`; - async validate(req: Request, profile: any): Promise { - if (this.strategyOptions.store.verifyCallback) { - return await this.strategyOptions.store.verifyCallback(this.container, req, profile, this.strict); + return class extends PassportStrategy(AzureStrategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: AzureAuthOptions, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + identityMetadata: strategyOptions.store.identityMetadata, + clientID: strategyOptions.store.clientID, + clientSecret: strategyOptions.store.clientSecret, + responseType: strategyOptions.store.responseType ?? ResponseType.Code, + responseMode: strategyOptions.store.responseMode ?? ResponseMode.Query, + redirectUrl: strategyOptions.store.callbackUrl, + allowHttpForRedirectUrl: strategyOptions.store.allowHttpForRedirectUrl ?? false, + validateIssuer: strategyOptions.store.validateIssuer ?? true, + isB2C: strategyOptions.store.isB2C ?? false, + issuer: strategyOptions.store.issuer, + passReqToCallback: true, + } as IOIDCStrategyOptionWithRequest); } - const authprofile: Profile = { - emails: [{ value: profile?.upn }], - name: { givenName: profile?.name?.givenName, familyName: profile?.name?.familyName }, - }; + async validate(req: Request, profile: IProfile): Promise { + if (this.strategyOptions.store.verifyCallback) { + return await this.strategyOptions.store.verifyCallback(this.container, req, profile, this.strict); + } - return await validateStoreCallback(authprofile, { - container: this.container, - strategyErrorIdentifier: 'azure_oidc', - strict: this.strict, - }); - } + const authprofile: Profile = { + emails: [{ value: profile?.upn }], + name: { givenName: profile?.name?.givenName, familyName: profile?.name?.familyName }, + }; + + return await validateStoreCallback(authprofile, { + container: this.container, + strategyErrorIdentifier: 'azure_oidc', + strategyName, + strict: this.strict, + }); + } + }; } /** * Return the router that hold the azure store authentication routes + * @param id * @param azure * @param configModule */ -export function getAzureStoreAuthRouter(azure: AzureAuthOptions, configModule: ConfigModule): Router { +export function getAzureStoreAuthRouter(id: string, azure: AzureAuthOptions, configModule: ConfigModule): Router { + const strategyName = `${AZURE_STORE_STRATEGY_NAME}_${id}`; return passportAuthRoutesBuilder({ domain: 'store', configModule, authPath: azure.store.authPath ?? '/store/auth/azure', authCallbackPath: azure.store.authCallbackPath ?? '/store/auth/azure/cb', successRedirect: azure.store.successRedirect, - strategyName: AZURE_STORE_STRATEGY_NAME, + strategyName: strategyName, passportAuthenticateMiddlewareOptions: { scope: azure.store.scope ?? [], }, diff --git a/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/types.ts b/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/types.ts index 08cf180b..87ea28a0 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/types.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/azure-oidc/types.ts @@ -1,5 +1,5 @@ import { MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AuthOptions } from '../../types'; +import { AuthProvider } from '../../types'; export const AZURE_STORE_STRATEGY_NAME = 'azure-oidc.store.medusa-auth-plugin'; export const AZURE_ADMIN_STRATEGY_NAME = 'azure-oidc.admin.medusa-auth-plugin'; @@ -92,13 +92,14 @@ export type AzureAuthOption = { container: MedusaContainer, req: Request, profile: any, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; }; export type AzureAuthOptions = { + type: 'azure_oidc'; admin?: AzureAuthOption; store?: AzureAuthOption; }; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/facebook/__tests__/admin/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/facebook/__tests__/admin/verify-callback.spec.ts index b302a0a7..5a6d2ebc 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/facebook/__tests__/admin/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/facebook/__tests__/admin/verify-callback.spec.ts @@ -1,9 +1,9 @@ import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { FacebookAdminStrategy } from '../../admin'; -import { AUTH_PROVIDER_KEY } from '../../../../types'; -import { FacebookAuthOptions, FACEBOOK_ADMIN_STRATEGY_NAME, Profile } from '../../types'; +import { AUTH_PROVIDER_KEY, IStrategy } from '../../../../types'; +import { FACEBOOK_ADMIN_STRATEGY_NAME, FacebookAuthOptions, Profile } from '../../types'; +import { getFacebookAdminStrategy } from '../../admin'; -describe('Facebook admin strategy verify callback', function () { +describe('Facebook admin strategy verify callback', function() { const existsEmail = 'exists@test.fr'; const existsEmailWithProviderKey = 'exist3s@test.fr'; const existsEmailWithWrongProviderKey = 'exist4s@test.fr'; @@ -13,7 +13,7 @@ describe('Facebook admin strategy verify callback', function () { let accessToken: string; let refreshToken: string; let profile: Profile; - let facebookAdminStrategy: FacebookAdminStrategy; + let facebookAdminStrategy: IStrategy; beforeEach(() => { profile = { @@ -35,7 +35,7 @@ describe('Facebook admin strategy verify callback', function () { return { id: 'test2', metadata: { - [AUTH_PROVIDER_KEY]: FACEBOOK_ADMIN_STRATEGY_NAME, + [AUTH_PROVIDER_KEY]: FACEBOOK_ADMIN_STRATEGY_NAME + '_test', }, }; } @@ -59,8 +59,9 @@ describe('Facebook admin strategy verify callback', function () { } as MedusaContainer; }); - describe('when strict is set to admin', function () { + describe('when strict is set to admin', function() { beforeEach(() => { + const FacebookAdminStrategy = getFacebookAdminStrategy('test'); facebookAdminStrategy = new FacebookAdminStrategy( container, {} as ConfigModule, @@ -69,7 +70,7 @@ describe('Facebook admin strategy verify callback', function () { clientSecret: 'fake', admin: {}, } as FacebookAuthOptions, - 'admin' + 'admin', ); }); @@ -86,7 +87,7 @@ describe('Facebook admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); }); @@ -124,8 +125,9 @@ describe('Facebook admin strategy verify callback', function () { }); }); - describe('when strict is set to store', function () { + describe('when strict is set to store', function() { beforeEach(() => { + const FacebookAdminStrategy = getFacebookAdminStrategy('test'); facebookAdminStrategy = new FacebookAdminStrategy( container, {} as ConfigModule, @@ -134,7 +136,7 @@ describe('Facebook admin strategy verify callback', function () { clientSecret: 'fake', admin: {}, } as FacebookAuthOptions, - 'store' + 'store', ); }); @@ -151,7 +153,7 @@ describe('Facebook admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); }); @@ -164,7 +166,7 @@ describe('Facebook admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); }); @@ -177,7 +179,7 @@ describe('Facebook admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); diff --git a/packages/medusa-plugin-auth/src/auth-strategies/facebook/__tests__/store/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/facebook/__tests__/store/verify-callback.spec.ts index 83fbfea7..72779fb6 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/facebook/__tests__/store/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/facebook/__tests__/store/verify-callback.spec.ts @@ -1,9 +1,9 @@ -import { FacebookStoreStrategy } from '../../store'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AUTH_PROVIDER_KEY, CUSTOMER_METADATA_KEY } from '../../../../types'; -import { FacebookAuthOptions, FACEBOOK_STORE_STRATEGY_NAME, Profile } from '../../types'; +import { AUTH_PROVIDER_KEY, CUSTOMER_METADATA_KEY, IStrategy } from '../../../../types'; +import { FACEBOOK_STORE_STRATEGY_NAME, FacebookAuthOptions, Profile } from '../../types'; +import { getFacebookStoreStrategy } from '../../store'; -describe('Facebook store strategy verify callback', function () { +describe('Facebook store strategy verify callback', function() { const existsEmail = 'exists@test.fr'; const existsEmailWithMeta = 'exist2s@test.fr'; const existsEmailWithMetaAndProviderKey = 'exist3s@test.fr'; @@ -14,7 +14,7 @@ describe('Facebook store strategy verify callback', function () { let accessToken: string; let refreshToken: string; let profile: Profile; - let facebookStoreStrategy: FacebookStoreStrategy; + let facebookStoreStrategy: IStrategy; let updateFn; let createFn; @@ -34,12 +34,12 @@ describe('Facebook store strategy verify callback', function () { resolve: (name: string): T => { const container_ = { manager: { - transaction: function (cb) { + transaction: function(cb) { return cb(); }, }, customerService: { - withTransaction: function () { + withTransaction: function() { return this; }, update: updateFn, @@ -65,7 +65,7 @@ describe('Facebook store strategy verify callback', function () { id: 'test3', metadata: { [CUSTOMER_METADATA_KEY]: true, - [AUTH_PROVIDER_KEY]: FACEBOOK_STORE_STRATEGY_NAME, + [AUTH_PROVIDER_KEY]: FACEBOOK_STORE_STRATEGY_NAME + '_test', }, }; } @@ -90,8 +90,9 @@ describe('Facebook store strategy verify callback', function () { } as MedusaContainer; }); - describe('when strict is set to store', function () { + describe('when strict is set to store', function() { beforeEach(() => { + const FacebookStoreStrategy = getFacebookStoreStrategy('test'); facebookStoreStrategy = new FacebookStoreStrategy( container, {} as ConfigModule, @@ -100,7 +101,7 @@ describe('Facebook store strategy verify callback', function () { clientSecret: 'fake', store: {}, } as FacebookAuthOptions, - 'store' + 'store', ); }); @@ -117,7 +118,7 @@ describe('Facebook store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); @@ -141,7 +142,7 @@ describe('Facebook store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); expect(updateFn).toHaveBeenCalledTimes(1); }); @@ -155,7 +156,7 @@ describe('Facebook store strategy verify callback', function () { .validate(req, accessToken, refreshToken, profile) .catch((err) => err); expect(err).toEqual( - new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`) + new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`), ); }); @@ -172,14 +173,15 @@ describe('Facebook store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); expect(createFn).toHaveBeenCalledTimes(1); }); }); - describe('when strict is set to admin', function () { + describe('when strict is set to admin', function() { beforeEach(() => { + const FacebookStoreStrategy = getFacebookStoreStrategy('test'); facebookStoreStrategy = new FacebookStoreStrategy( container, {} as ConfigModule, @@ -188,7 +190,7 @@ describe('Facebook store strategy verify callback', function () { clientSecret: 'fake', store: {}, } as FacebookAuthOptions, - 'admin' + 'admin', ); }); @@ -205,7 +207,7 @@ describe('Facebook store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); @@ -218,7 +220,7 @@ describe('Facebook store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); }); @@ -231,7 +233,7 @@ describe('Facebook store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); expect(updateFn).toHaveBeenCalledTimes(1); }); @@ -245,7 +247,7 @@ describe('Facebook store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test4', - }) + }), ); }); @@ -262,7 +264,7 @@ describe('Facebook store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); expect(createFn).toHaveBeenCalledTimes(1); }); diff --git a/packages/medusa-plugin-auth/src/auth-strategies/facebook/admin.ts b/packages/medusa-plugin-auth/src/auth-strategies/facebook/admin.ts index 53edb91d..3f6b5db4 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/facebook/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/facebook/admin.ts @@ -1,66 +1,76 @@ -import { Strategy as FacebookStrategy } from 'passport-facebook'; +import { Strategy as FacebookStrategy, StrategyOptionsWithRequest } from 'passport-facebook'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; import { Router } from 'express'; import { FACEBOOK_ADMIN_STRATEGY_NAME, FacebookAuthOptions, Profile } from './types'; import { PassportStrategy } from '../../core/passport/Strategy'; import { validateAdminCallback } from '../../core/validate-callback'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class FacebookAdminStrategy extends PassportStrategy(FacebookStrategy, FACEBOOK_ADMIN_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: FacebookAuthOptions, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - clientID: strategyOptions.clientID, - clientSecret: strategyOptions.clientSecret, - callbackURL: strategyOptions.admin.callbackUrl, - passReqToCallback: true, - profileFields: ['id', 'displayName', 'email', 'gender', 'name'], - }); - } - - async validate( - req: Request, - accessToken: string, - refreshToken: string, - profile: Profile - ): Promise { - if (this.strategyOptions.admin.verifyCallback) { - return await this.strategyOptions.admin.verifyCallback( - this.container, - req, - accessToken, - refreshToken, - profile, - this.strict - ); +export function getFacebookAdminStrategy(id: string): StrategyFactory { + const strategyName = `${FACEBOOK_ADMIN_STRATEGY_NAME}_${id}`; + return class extends PassportStrategy(FacebookStrategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: FacebookAuthOptions, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + clientID: strategyOptions.clientID, + clientSecret: strategyOptions.clientSecret, + callbackURL: strategyOptions.admin.callbackUrl, + passReqToCallback: true, + profileFields: ['id', 'displayName', 'email', 'gender', 'name'], + } as StrategyOptionsWithRequest); } - return await validateAdminCallback(profile, { - container: this.container, - strategyErrorIdentifier: 'facebook', - strict: this.strict, - }); - } + async validate( + req: Request, + accessToken: string, + refreshToken: string, + profile: Profile, + ): Promise { + if (this.strategyOptions.admin.verifyCallback) { + return await this.strategyOptions.admin.verifyCallback( + this.container, + req, + accessToken, + refreshToken, + profile, + this.strict, + ); + } + + return await validateAdminCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'facebook', + strict: this.strict, + strategyName, + }); + } + }; } /** * Return the router that hold the facebook admin authentication routes + * @param id * @param facebook * @param configModule */ -export function getFacebookAdminAuthRouter(facebook: FacebookAuthOptions, configModule: ConfigModule): Router { +export function getFacebookAdminAuthRouter( + id: string, + facebook: FacebookAuthOptions, + configModule: ConfigModule, +): Router { + const strategyName = `${FACEBOOK_ADMIN_STRATEGY_NAME}_${id}`; return passportAuthRoutesBuilder({ domain: 'admin', configModule, authPath: facebook.admin.authPath ?? '/admin/auth/facebook', authCallbackPath: facebook.admin.authCallbackPath ?? '/admin/auth/facebook/cb', successRedirect: facebook.admin.successRedirect, - strategyName: FACEBOOK_ADMIN_STRATEGY_NAME, + strategyName, passportAuthenticateMiddlewareOptions: { scope: ['email'], }, diff --git a/packages/medusa-plugin-auth/src/auth-strategies/facebook/index.ts b/packages/medusa-plugin-auth/src/auth-strategies/facebook/index.ts index 45ade916..78f7d0f0 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/facebook/index.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/facebook/index.ts @@ -1,34 +1,38 @@ -import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AuthOptions, StrategyExport } from '../../types'; +import { StrategyExport } from '../../types'; import { Router } from 'express'; -import { FacebookAdminStrategy, getFacebookAdminAuthRouter } from './admin'; -import { FacebookStoreStrategy, getFacebookStoreAuthRouter } from './store'; +import { getFacebookAdminAuthRouter, getFacebookAdminStrategy } from './admin'; +import { getFacebookStoreAuthRouter, getFacebookStoreStrategy } from './store'; +import { FacebookAuthOptions } from './types'; export * from './admin'; export * from './store'; export * from './types'; export default { - load: (container: MedusaContainer, configModule: ConfigModule, options: AuthOptions): void => { - if (options.facebook?.admin) { - new FacebookAdminStrategy(container, configModule, options.facebook, options.strict); + load: (container, configModule, options): void => { + const id = options.identifier ?? options.type; + if (options.admin) { + const FacebookAdminStrategy = getFacebookAdminStrategy(id); + new FacebookAdminStrategy(container, configModule, options, options.strict); } - if (options.facebook?.store) { - new FacebookStoreStrategy(container, configModule, options.facebook, options.strict); + if (options.store) { + const FacebookStoreStrategy = getFacebookStoreStrategy(id); + new FacebookStoreStrategy(container, configModule, options, options.strict); } }, - getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { + getRouter: (configModule, options): Router[] => { + const id = options.identifier ?? options.type; const routers = []; - if (options.facebook?.admin) { - routers.push(getFacebookAdminAuthRouter(options.facebook, configModule)); + if (options.admin) { + routers.push(getFacebookAdminAuthRouter(id, options, configModule)); } - if (options.facebook?.store) { - routers.push(getFacebookStoreAuthRouter(options.facebook, configModule)); + if (options.store) { + routers.push(getFacebookStoreAuthRouter(id, options, configModule)); } return routers; }, -} as StrategyExport; +} as StrategyExport; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/facebook/store.ts b/packages/medusa-plugin-auth/src/auth-strategies/facebook/store.ts index 7f845ea7..aaec9936 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/facebook/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/facebook/store.ts @@ -1,66 +1,76 @@ import { Router } from 'express'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { Strategy as FacebookStrategy } from 'passport-facebook'; +import { Strategy as FacebookStrategy, StrategyOptionsWithRequest } from 'passport-facebook'; import { FACEBOOK_STORE_STRATEGY_NAME, FacebookAuthOptions, Profile } from './types'; import { PassportStrategy } from '../../core/passport/Strategy'; import { validateStoreCallback } from '../../core/validate-callback'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class FacebookStoreStrategy extends PassportStrategy(FacebookStrategy, FACEBOOK_STORE_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: FacebookAuthOptions, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - clientID: strategyOptions.clientID, - clientSecret: strategyOptions.clientSecret, - callbackURL: strategyOptions.store.callbackUrl, - passReqToCallback: true, - profileFields: ['id', 'displayName', 'email', 'gender', 'name'], - }); - } - - async validate( - req: Request, - accessToken: string, - refreshToken: string, - profile: Profile - ): Promise { - if (this.strategyOptions.store.verifyCallback) { - return await this.strategyOptions.store.verifyCallback( - this.container, - req, - accessToken, - refreshToken, - profile, - this.strict - ); +export function getFacebookStoreStrategy(id: string): StrategyFactory { + const strategyName = `${FACEBOOK_STORE_STRATEGY_NAME}_${id}`; + return class extends PassportStrategy(FacebookStrategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: FacebookAuthOptions, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + clientID: strategyOptions.clientID, + clientSecret: strategyOptions.clientSecret, + callbackURL: strategyOptions.store.callbackUrl, + passReqToCallback: true, + profileFields: ['id', 'displayName', 'email', 'gender', 'name'], + } as StrategyOptionsWithRequest); } - return await validateStoreCallback(profile, { - container: this.container, - strategyErrorIdentifier: 'facebook', - strict: this.strict, - }); - } + async validate( + req: Request, + accessToken: string, + refreshToken: string, + profile: Profile, + ): Promise { + if (this.strategyOptions.store.verifyCallback) { + return await this.strategyOptions.store.verifyCallback( + this.container, + req, + accessToken, + refreshToken, + profile, + this.strict, + ); + } + + return await validateStoreCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'facebook', + strict: this.strict, + strategyName, + }); + } + }; } /** * Return the router that hold the facebook store authentication routes + * @param id * @param facebook * @param configModule */ -export function getFacebookStoreAuthRouter(facebook: FacebookAuthOptions, configModule: ConfigModule): Router { +export function getFacebookStoreAuthRouter( + id: string, + facebook: FacebookAuthOptions, + configModule: ConfigModule, +): Router { + const strategyName = `${FACEBOOK_STORE_STRATEGY_NAME}_${id}`; return passportAuthRoutesBuilder({ domain: 'store', configModule, authPath: facebook.store.authPath ?? '/store/auth/facebook', authCallbackPath: facebook.store.authCallbackPath ?? '/store/auth/facebook/cb', successRedirect: facebook.store.successRedirect, - strategyName: FACEBOOK_STORE_STRATEGY_NAME, + strategyName, passportAuthenticateMiddlewareOptions: { scope: ['email'], }, diff --git a/packages/medusa-plugin-auth/src/auth-strategies/facebook/types.ts b/packages/medusa-plugin-auth/src/auth-strategies/facebook/types.ts index 9a91eed0..ce1e2070 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/facebook/types.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/facebook/types.ts @@ -1,5 +1,5 @@ import { MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AuthOptions } from '../../types'; +import { AuthProvider } from '../../types'; export const FACEBOOK_ADMIN_STRATEGY_NAME = 'facebook.admin.medusa-auth-plugin'; export const FACEBOOK_STORE_STRATEGY_NAME = 'facebook.store.medusa-auth-plugin'; @@ -7,6 +7,7 @@ export const FACEBOOK_STORE_STRATEGY_NAME = 'facebook.store.medusa-auth-plugin'; export type Profile = { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }; export type FacebookAuthOptions = { + type: 'facebook'; clientID: string; clientSecret: string; admin?: { @@ -30,7 +31,7 @@ export type FacebookAuthOptions = { accessToken: string, refreshToken: string, profile: Profile, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; @@ -56,7 +57,7 @@ export type FacebookAuthOptions = { accessToken: string, refreshToken: string, profile: Profile, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/firebase/admin.ts b/packages/medusa-plugin-auth/src/auth-strategies/firebase/admin.ts index 69edc3d7..b8c4a31e 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/firebase/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/firebase/admin.ts @@ -6,47 +6,57 @@ import { PassportStrategy } from '../../core/passport/Strategy'; import { validateAdminCallback } from '../../core/validate-callback'; import { firebaseAuthRoutesBuilder } from './utils'; import { auth } from 'firebase-admin'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class FirebaseAdminStrategy extends PassportStrategy(FirebaseStrategy, FIREBASE_ADMIN_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: FirebaseAuthOptions, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - jwtFromRequest: strategyOptions.store.jwtFromRequest ?? ExtractJwt.fromAuthHeaderAsBearerToken(), - }); - } +export function getFirebaseAdminStrategy(id: string): StrategyFactory { + const strategyName = `${FIREBASE_ADMIN_STRATEGY_NAME}_${id}`; + return class FirebaseAdminStrategy extends PassportStrategy(FirebaseStrategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: FirebaseAuthOptions, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + jwtFromRequest: strategyOptions.store.jwtFromRequest ?? ExtractJwt.fromAuthHeaderAsBearerToken(), + }); + } - async validate(token: string): Promise { - const decodedToken = await auth().verifyIdToken(token); + async validate(token: string): Promise { + const decodedToken = await auth().verifyIdToken(token); - if (this.strategyOptions.admin.verifyCallback) { - return await this.strategyOptions.admin.verifyCallback(this.container, decodedToken, this.strict); - } + if (this.strategyOptions.admin.verifyCallback) { + return await this.strategyOptions.admin.verifyCallback(this.container, decodedToken, this.strict); + } - const profile: Profile = { emails: [{ value: decodedToken.email }] }; - return await validateAdminCallback(profile, { - container: this.container, - strategyErrorIdentifier: 'firebase', - strict: this.strict, - }); - } + const profile: Profile = { emails: [{ value: decodedToken.email }] }; + return await validateAdminCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'firebase', + strict: this.strict, + strategyName, + }); + } + }; } /** * Return the router that hold the firebase admin authentication routes + * @param id * @param firebase * @param configModule */ -export function getFirebaseAdminAuthRouter(firebase: FirebaseAuthOptions, configModule: ConfigModule): Router { +export function getFirebaseAdminAuthRouter( + id: string, + firebase: FirebaseAuthOptions, + configModule: ConfigModule, +): Router { + const strategyName = `${FIREBASE_ADMIN_STRATEGY_NAME}_${id}`; return firebaseAuthRoutesBuilder({ domain: 'admin', configModule, authPath: firebase.admin.authPath ?? '/admin/auth/firebase', - strategyName: FIREBASE_ADMIN_STRATEGY_NAME, + strategyName, expiresIn: firebase.admin.expiresIn, }); } diff --git a/packages/medusa-plugin-auth/src/auth-strategies/firebase/index.ts b/packages/medusa-plugin-auth/src/auth-strategies/firebase/index.ts index ce1c275d..27e354e4 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/firebase/index.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/firebase/index.ts @@ -1,55 +1,56 @@ -import { AuthOptions, StrategyExport } from '../../types'; +import { StrategyExport } from '../../types'; import { Router } from 'express'; -import { getFirebaseAdminAuthRouter, FirebaseAdminStrategy } from './admin'; -import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { getFirebaseStoreAuthRouter, FirebaseStoreStrategy } from './store'; +import { getFirebaseAdminAuthRouter, getFirebaseAdminStrategy } from './admin'; +import { getFirebaseStoreAuthRouter, getFirebaseStoreStrategy } from './store'; import { initializeApp } from 'firebase-admin/app'; import { credential } from 'firebase-admin'; +import { FirebaseAuthOptions } from './types'; export * from './types'; export * from './admin'; export * from './store'; export default { - load: (container: MedusaContainer, configModule: ConfigModule, options: AuthOptions): void => { - // initialize firebase admin sdk - if (options.firebase) { - if (!options.firebase.credentialJsonPath) { - throw new Error('Firebase authentication requires credentialJsonPath, but it has not been provided.'); - } - - try { - const cred = credential.cert(options.firebase.credentialJsonPath); - - initializeApp({ - credential: cred, - }); - } catch (error) { - throw new Error( - 'Firebase authentication failed to initialize. Please check your credentialJsonPath and JSON file.' - ); - } + load: (container, configModule, options): void => { + const id = options.identifier ?? options.type; + if (!options.credentialJsonPath) { + throw new Error('Firebase authentication requires credentialJsonPath, but it has not been provided.'); } - if (options.firebase?.admin) { - new FirebaseAdminStrategy(container, configModule, options.firebase, options.strict); + try { + const cred = credential.cert(options.credentialJsonPath); + + initializeApp({ + credential: cred, + }); + } catch (error) { + throw new Error( + 'Firebase authentication failed to initialize. Please check your credentialJsonPath and JSON file.', + ); + } + + if (options.admin) { + const Clazz = getFirebaseAdminStrategy(id); + new Clazz(container, configModule, options, options.strict); } - if (options.firebase?.store) { - new FirebaseStoreStrategy(container, configModule, options.firebase, options.strict); + if (options.store) { + const Clazz = getFirebaseStoreStrategy(id); + new Clazz(container, configModule, options, options.strict); } }, - getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { + getRouter: (configModule, options): Router[] => { + const id = options.identifier ?? options.type; const routers = []; - if (options.firebase?.admin) { - routers.push(getFirebaseAdminAuthRouter(options.firebase, configModule)); + if (options.admin) { + routers.push(getFirebaseAdminAuthRouter(id, options, configModule)); } - if (options.firebase?.store) { - routers.push(getFirebaseStoreAuthRouter(options.firebase, configModule)); + if (options.store) { + routers.push(getFirebaseStoreAuthRouter(id, options, configModule)); } return routers; }, -} as StrategyExport; +} as StrategyExport; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/firebase/store.ts b/packages/medusa-plugin-auth/src/auth-strategies/firebase/store.ts index 1c2e42ce..7286e1bd 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/firebase/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/firebase/store.ts @@ -1,52 +1,62 @@ import { Router } from 'express'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { Strategy as FirebaseStrategy, ExtractJwt } from 'passport-firebase-jwt'; +import { ExtractJwt, Strategy as FirebaseStrategy } from 'passport-firebase-jwt'; import { PassportStrategy } from '../../core/passport/Strategy'; import { validateStoreCallback } from '../../core/validate-callback'; import { FIREBASE_STORE_STRATEGY_NAME, FirebaseAuthOptions, Profile } from './types'; import { firebaseAuthRoutesBuilder } from './utils'; import { auth } from 'firebase-admin'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class FirebaseStoreStrategy extends PassportStrategy(FirebaseStrategy, FIREBASE_STORE_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: FirebaseAuthOptions, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - jwtFromRequest: strategyOptions.store.jwtFromRequest ?? ExtractJwt.fromAuthHeaderAsBearerToken(), - }); - } +export function getFirebaseStoreStrategy(id: string): StrategyFactory { + const strategyName = `${FIREBASE_STORE_STRATEGY_NAME}_${id}`; + return class FirebaseStoreStrategy extends PassportStrategy(FirebaseStrategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: FirebaseAuthOptions, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + jwtFromRequest: strategyOptions.store.jwtFromRequest ?? ExtractJwt.fromAuthHeaderAsBearerToken(), + }); + } - async validate(token: string): Promise { - const decodedToken = await auth().verifyIdToken(token); + async validate(token: string): Promise { + const decodedToken = await auth().verifyIdToken(token); - if (this.strategyOptions.store.verifyCallback) { - return await this.strategyOptions.store.verifyCallback(this.container, decodedToken, this.strict); - } + if (this.strategyOptions.store.verifyCallback) { + return await this.strategyOptions.store.verifyCallback(this.container, decodedToken, this.strict); + } - const profile: Profile = { emails: [{ value: decodedToken.email }] }; - return await validateStoreCallback(profile, { - container: this.container, - strategyErrorIdentifier: 'firebase', - strict: this.strict, - }); - } + const profile: Profile = { emails: [{ value: decodedToken.email }] }; + return await validateStoreCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'firebase', + strict: this.strict, + strategyName, + }); + } + }; } /** * Return the router that hold the firebase store authentication routes + * @param id * @param firebase * @param configModule */ -export function getFirebaseStoreAuthRouter(firebase: FirebaseAuthOptions, configModule: ConfigModule): Router { +export function getFirebaseStoreAuthRouter( + id: string, + firebase: FirebaseAuthOptions, + configModule: ConfigModule, +): Router { + const strategyName = `${FIREBASE_STORE_STRATEGY_NAME}_${id}`; return firebaseAuthRoutesBuilder({ domain: 'store', configModule, authPath: firebase.store.authPath ?? '/store/auth/firebase', - strategyName: FIREBASE_STORE_STRATEGY_NAME, + strategyName, expiresIn: firebase.store.expiresIn, }); } diff --git a/packages/medusa-plugin-auth/src/auth-strategies/firebase/types.ts b/packages/medusa-plugin-auth/src/auth-strategies/firebase/types.ts index 2a9514cf..6797d4f0 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/firebase/types.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/firebase/types.ts @@ -1,5 +1,5 @@ import { MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AuthOptions } from '../../types'; +import { AuthProvider } from '../../types'; export const FIREBASE_STORE_STRATEGY_NAME = 'firebase.store.medusa-auth-plugin'; export const FIREBASE_ADMIN_STRATEGY_NAME = 'firebase.admin.medusa-auth-plugin'; @@ -7,6 +7,7 @@ export const FIREBASE_ADMIN_STRATEGY_NAME = 'firebase.admin.medusa-auth-plugin'; export type Profile = { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }; export type FirebaseAuthOptions = { + type: 'firebase'; credentialJsonPath: string; admin?: { /** @@ -23,7 +24,7 @@ export type FirebaseAuthOptions = { verifyCallback?: ( container: MedusaContainer, decodedToken: any, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; @@ -43,7 +44,7 @@ export type FirebaseAuthOptions = { verifyCallback?: ( container: MedusaContainer, decodedToken: any, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/firebase/utils.ts b/packages/medusa-plugin-auth/src/auth-strategies/firebase/utils.ts index 809c93f7..d1cb7b1a 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/firebase/utils.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/firebase/utils.ts @@ -1,7 +1,7 @@ import passport from 'passport'; import cors from 'cors'; import { ConfigModule } from '@medusajs/medusa/dist/types/global'; -import { Router, Request, Response } from 'express'; +import { Request, Response, Router } from 'express'; import { authenticateSessionFactory, signToken } from '../../core/auth-callback-middleware'; function firebaseCallbackMiddleware(domain: 'admin' | 'store', configModule: ConfigModule, expiresIn?: number) { @@ -19,12 +19,12 @@ function firebaseCallbackMiddleware(domain: 'admin' | 'store', configModule: Con } export function firebaseAuthRoutesBuilder({ - domain, - configModule, - authPath, - strategyName, - expiresIn, -}: { + domain, + configModule, + authPath, + strategyName, + expiresIn, + }: { domain: 'admin' | 'store'; configModule: ConfigModule; authPath: string; @@ -52,7 +52,7 @@ export function firebaseAuthRoutesBuilder({ passport.authenticate(strategyName, { session: false, }), - callbackHandler + callbackHandler, ); return router; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/google/__tests__/admin/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/google/__tests__/admin/verify-callback.spec.ts index bb41effd..e6da8eb5 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/google/__tests__/admin/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/google/__tests__/admin/verify-callback.spec.ts @@ -1,9 +1,9 @@ import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { GoogleAdminStrategy } from '../../admin'; -import { AUTH_PROVIDER_KEY } from '../../../../types'; -import { GoogleAuthOptions, GOOGLE_ADMIN_STRATEGY_NAME } from '../../types'; +import { AUTH_PROVIDER_KEY, IStrategy } from '../../../../types'; +import { GOOGLE_ADMIN_STRATEGY_NAME, GoogleAuthOptions } from '../../types'; +import { getGoogleAdminStrategy } from '../../admin'; -describe('Google admin strategy verify callback', function () { +describe('Google admin strategy verify callback', function() { const existsEmail = 'exists@test.fr'; const existsEmailWithProviderKey = 'exist3s@test.fr'; const existsEmailWithWrongProviderKey = 'exist4s@test.fr'; @@ -13,7 +13,7 @@ describe('Google admin strategy verify callback', function () { let accessToken: string; let refreshToken: string; let profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }; - let googleAdminStrategy: GoogleAdminStrategy; + let googleAdminStrategy: IStrategy; beforeEach(() => { profile = { @@ -35,7 +35,7 @@ describe('Google admin strategy verify callback', function () { return { id: 'test2', metadata: { - [AUTH_PROVIDER_KEY]: GOOGLE_ADMIN_STRATEGY_NAME, + [AUTH_PROVIDER_KEY]: GOOGLE_ADMIN_STRATEGY_NAME + '_test' }, }; } @@ -59,13 +59,14 @@ describe('Google admin strategy verify callback', function () { } as MedusaContainer; }); - describe('when strict is set to admin', function () { + describe('when strict is set to admin', function() { beforeEach(() => { + const GoogleAdminStrategy = getGoogleAdminStrategy('test'); googleAdminStrategy = new GoogleAdminStrategy( container, {} as ConfigModule, { clientID: 'fake', clientSecret: 'fake', admin: {} } as GoogleAuthOptions, - 'admin' + 'admin', ); }); @@ -82,7 +83,7 @@ describe('Google admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); }); @@ -114,13 +115,14 @@ describe('Google admin strategy verify callback', function () { }); }); - describe('when strict is set for store only', function () { + describe('when strict is set for store only', function() { beforeEach(() => { + const GoogleAdminStrategy = getGoogleAdminStrategy('test'); googleAdminStrategy = new GoogleAdminStrategy( container, {} as ConfigModule, { clientID: 'fake', clientSecret: 'fake', admin: {} } as GoogleAuthOptions, - 'store' + 'store', ); }); @@ -137,7 +139,7 @@ describe('Google admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); }); @@ -150,7 +152,7 @@ describe('Google admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); }); @@ -163,7 +165,7 @@ describe('Google admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); diff --git a/packages/medusa-plugin-auth/src/auth-strategies/google/__tests__/store/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/google/__tests__/store/verify-callback.spec.ts index 0215eccf..284158ad 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/google/__tests__/store/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/google/__tests__/store/verify-callback.spec.ts @@ -1,9 +1,9 @@ -import { GoogleStoreStrategy } from '../../store'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AUTH_PROVIDER_KEY, CUSTOMER_METADATA_KEY } from '../../../../types'; -import { GoogleAuthOptions, GOOGLE_STORE_STRATEGY_NAME, Profile } from '../../types'; +import { AUTH_PROVIDER_KEY, CUSTOMER_METADATA_KEY, IStrategy } from '../../../../types'; +import { GOOGLE_STORE_STRATEGY_NAME, GoogleAuthOptions, Profile } from '../../types'; +import { getGoogleStoreStrategy } from '../../store'; -describe('Google store strategy verify callback', function () { +describe('Google store strategy verify callback', function() { const existsEmail = 'exists@test.fr'; const existsEmailWithMeta = 'exist2s@test.fr'; const existsEmailWithMetaAndProviderKey = 'exist3s@test.fr'; @@ -14,7 +14,7 @@ describe('Google store strategy verify callback', function () { let accessToken: string; let refreshToken: string; let profile: Profile; - let googleStoreStrategy: GoogleStoreStrategy; + let googleStoreStrategy: IStrategy; let updateFn; let createFn; @@ -34,12 +34,12 @@ describe('Google store strategy verify callback', function () { resolve: (name: string): T => { const container_ = { manager: { - transaction: function (cb) { + transaction: function(cb) { return cb(); }, }, customerService: { - withTransaction: function () { + withTransaction: function() { return this; }, update: updateFn, @@ -65,7 +65,7 @@ describe('Google store strategy verify callback', function () { id: 'test3', metadata: { [CUSTOMER_METADATA_KEY]: true, - [AUTH_PROVIDER_KEY]: GOOGLE_STORE_STRATEGY_NAME, + [AUTH_PROVIDER_KEY]: GOOGLE_STORE_STRATEGY_NAME + '_test', }, }; } @@ -90,13 +90,14 @@ describe('Google store strategy verify callback', function () { } as MedusaContainer; }); - describe('when strict is set to store', function () { + describe('when strict is set to store', function() { beforeEach(() => { + const GoogleStoreStrategy = getGoogleStoreStrategy('test'); googleStoreStrategy = new GoogleStoreStrategy( container, {} as ConfigModule, { clientID: 'fake', clientSecret: 'fake', store: {} } as GoogleAuthOptions, - 'store' + 'store', ); }); @@ -113,7 +114,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); @@ -135,7 +136,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); expect(updateFn).toHaveBeenCalledTimes(1); }); @@ -147,7 +148,7 @@ describe('Google store strategy verify callback', function () { const err = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); expect(err).toEqual( - new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`) + new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`), ); }); @@ -164,19 +165,20 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); expect(createFn).toHaveBeenCalledTimes(1); }); }); - describe('when strict is set to admin only', function () { + describe('when strict is set to admin only', function() { beforeEach(() => { + const GoogleStoreStrategy = getGoogleStoreStrategy('test'); googleStoreStrategy = new GoogleStoreStrategy( container, {} as ConfigModule, { clientID: 'fake', clientSecret: 'fake', store: {} } as GoogleAuthOptions, - 'admin' + 'admin', ); }); @@ -193,7 +195,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); @@ -206,7 +208,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); }); @@ -219,7 +221,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); expect(updateFn).toHaveBeenCalledTimes(1); }); @@ -233,7 +235,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test4', - }) + }), ); }); @@ -250,7 +252,7 @@ describe('Google store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); expect(createFn).toHaveBeenCalledTimes(1); }); diff --git a/packages/medusa-plugin-auth/src/auth-strategies/google/admin.ts b/packages/medusa-plugin-auth/src/auth-strategies/google/admin.ts index 6a68ffe8..c00d58d0 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/google/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/google/admin.ts @@ -1,65 +1,71 @@ -import { Strategy as GoogleStrategy } from 'passport-google-oauth2'; +import { Strategy as GoogleStrategy, StrategyOptionsWithRequest } from 'passport-google-oauth2'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; import { Router } from 'express'; import { GOOGLE_ADMIN_STRATEGY_NAME, GoogleAuthOptions, Profile } from './types'; import { PassportStrategy } from '../../core/passport/Strategy'; import { validateAdminCallback } from '../../core/validate-callback'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class GoogleAdminStrategy extends PassportStrategy(GoogleStrategy, GOOGLE_ADMIN_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: GoogleAuthOptions, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - clientID: strategyOptions.clientID, - clientSecret: strategyOptions.clientSecret, - callbackURL: strategyOptions.admin.callbackUrl, - passReqToCallback: true, - }); - } - - async validate( - req: Request, - accessToken: string, - refreshToken: string, - profile: Profile - ): Promise { - if (this.strategyOptions.admin.verifyCallback) { - return await this.strategyOptions.admin.verifyCallback( - this.container, - req, - accessToken, - refreshToken, - profile, - this.strict - ); +export function getGoogleAdminStrategy(id: string): StrategyFactory { + const strategyName = `${GOOGLE_ADMIN_STRATEGY_NAME}_${id}`; + return class GoogleAdminStrategy extends PassportStrategy(GoogleStrategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: GoogleAuthOptions, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + clientID: strategyOptions.clientID, + clientSecret: strategyOptions.clientSecret, + callbackURL: strategyOptions.admin.callbackUrl, + passReqToCallback: true, + } as StrategyOptionsWithRequest); } - return await validateAdminCallback(profile, { - container: this.container, - strategyErrorIdentifier: 'google', - strict: this.strict, - }); - } + async validate( + req: Request, + accessToken: string, + refreshToken: string, + profile: Profile, + ): Promise { + if (this.strategyOptions.admin.verifyCallback) { + return await this.strategyOptions.admin.verifyCallback( + this.container, + req, + accessToken, + refreshToken, + profile, + this.strict, + ); + } + + return await validateAdminCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'google', + strict: this.strict, + strategyName, + }); + } + }; } /** * Return the router that hold the google admin authentication routes + * @param id * @param google * @param configModule */ -export function getGoogleAdminAuthRouter(google: GoogleAuthOptions, configModule: ConfigModule): Router { +export function getGoogleAdminAuthRouter(id: string, google: GoogleAuthOptions, configModule: ConfigModule): Router { + const strategyName = `${GOOGLE_ADMIN_STRATEGY_NAME}_${id}`; return passportAuthRoutesBuilder({ domain: 'admin', configModule, authPath: google.admin.authPath ?? '/admin/auth/google', authCallbackPath: google.admin.authCallbackPath ?? '/admin/auth/google/cb', successRedirect: google.admin.successRedirect, - strategyName: GOOGLE_ADMIN_STRATEGY_NAME, + strategyName, passportAuthenticateMiddlewareOptions: { scope: [ 'https://www.googleapis.com/auth/userinfo.email', diff --git a/packages/medusa-plugin-auth/src/auth-strategies/google/index.ts b/packages/medusa-plugin-auth/src/auth-strategies/google/index.ts index 1f83ba02..76b89981 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/google/index.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/google/index.ts @@ -1,34 +1,38 @@ -import { AuthOptions, StrategyExport } from '../../types'; +import { StrategyExport } from '../../types'; import { Router } from 'express'; -import { getGoogleAdminAuthRouter, GoogleAdminStrategy } from './admin'; -import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { getGoogleStoreAuthRouter, GoogleStoreStrategy } from './store'; +import { getGoogleAdminAuthRouter, getGoogleAdminStrategy } from './admin'; +import { getGoogleStoreAuthRouter, getGoogleStoreStrategy } from './store'; +import { GoogleAuthOptions } from './types'; export * from './types'; export * from './admin'; export * from './store'; export default { - load: (container: MedusaContainer, configModule: ConfigModule, options: AuthOptions): void => { - if (options.google?.admin) { - new GoogleAdminStrategy(container, configModule, options.google, options.strict); + load: (container, configModule, options): void => { + const id = options.identifier ?? options.type; + if (options.admin) { + const Clazz = getGoogleAdminStrategy(id); + new Clazz(container, configModule, options, options.strict); } - if (options.google?.store) { - new GoogleStoreStrategy(container, configModule, options.google, options.strict); + if (options.store) { + const Clazz = getGoogleStoreStrategy(id); + new Clazz(container, configModule, options, options.strict); } }, - getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { + getRouter: (configModule, options): Router[] => { + const id = options.identifier ?? options.type; const routers = []; - if (options.google?.admin) { - routers.push(getGoogleAdminAuthRouter(options.google, configModule)); + if (options.admin) { + routers.push(getGoogleAdminAuthRouter(id, options, configModule)); } - if (options.google?.store) { - routers.push(getGoogleStoreAuthRouter(options.google, configModule)); + if (options.store) { + routers.push(getGoogleStoreAuthRouter(id, options, configModule)); } return routers; }, -} as StrategyExport; +} as StrategyExport; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/google/store.ts b/packages/medusa-plugin-auth/src/auth-strategies/google/store.ts index 50f1042d..6ab371b4 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/google/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/google/store.ts @@ -1,65 +1,71 @@ import { Router } from 'express'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { Strategy as GoogleStrategy } from 'passport-google-oauth2'; +import { Strategy as GoogleStrategy, StrategyOptionsWithRequest } from 'passport-google-oauth2'; import { PassportStrategy } from '../../core/passport/Strategy'; import { GOOGLE_STORE_STRATEGY_NAME, GoogleAuthOptions, Profile } from './types'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; import { validateStoreCallback } from '../../core/validate-callback'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class GoogleStoreStrategy extends PassportStrategy(GoogleStrategy, GOOGLE_STORE_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: GoogleAuthOptions, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - clientID: strategyOptions.clientID, - clientSecret: strategyOptions.clientSecret, - callbackURL: strategyOptions.store.callbackUrl, - passReqToCallback: true, - }); - } - - async validate( - req: Request, - accessToken: string, - refreshToken: string, - profile: Profile - ): Promise { - if (this.strategyOptions.store.verifyCallback) { - return await this.strategyOptions.store.verifyCallback( - this.container, - req, - accessToken, - refreshToken, - profile, - this.strict - ); +export function getGoogleStoreStrategy(id: string): StrategyFactory { + const strategyName = `${GOOGLE_STORE_STRATEGY_NAME}_${id}`; + return class GoogleStoreStrategy extends PassportStrategy(GoogleStrategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: GoogleAuthOptions, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + clientID: strategyOptions.clientID, + clientSecret: strategyOptions.clientSecret, + callbackURL: strategyOptions.store.callbackUrl, + passReqToCallback: true, + } as StrategyOptionsWithRequest); } - return await validateStoreCallback(profile, { - container: this.container, - strategyErrorIdentifier: 'google', - strict: this.strict, - }); - } + async validate( + req: Request, + accessToken: string, + refreshToken: string, + profile: Profile, + ): Promise { + if (this.strategyOptions.store.verifyCallback) { + return await this.strategyOptions.store.verifyCallback( + this.container, + req, + accessToken, + refreshToken, + profile, + this.strict, + ); + } + + return await validateStoreCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'google', + strict: this.strict, + strategyName, + }); + } + }; } /** * Return the router that hold the google store authentication routes + * @param id * @param google * @param configModule */ -export function getGoogleStoreAuthRouter(google: GoogleAuthOptions, configModule: ConfigModule): Router { +export function getGoogleStoreAuthRouter(id: string, google: GoogleAuthOptions, configModule: ConfigModule): Router { + const strategyName = `${GOOGLE_STORE_STRATEGY_NAME}_${id}`; return passportAuthRoutesBuilder({ domain: 'store', configModule, authPath: google.store.authPath ?? '/store/auth/google', authCallbackPath: google.store.authCallbackPath ?? '/store/auth/google/cb', successRedirect: google.store.successRedirect, - strategyName: GOOGLE_STORE_STRATEGY_NAME, + strategyName, passportAuthenticateMiddlewareOptions: { scope: [ 'https://www.googleapis.com/auth/userinfo.email', diff --git a/packages/medusa-plugin-auth/src/auth-strategies/google/types.ts b/packages/medusa-plugin-auth/src/auth-strategies/google/types.ts index e4a5cd5e..c0bf7738 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/google/types.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/google/types.ts @@ -1,5 +1,5 @@ import { MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AuthOptions } from '../../types'; +import { AuthProvider } from '../../types'; export const GOOGLE_STORE_STRATEGY_NAME = 'google.store.medusa-auth-plugin'; export const GOOGLE_ADMIN_STRATEGY_NAME = 'google.admin.medusa-auth-plugin'; @@ -7,6 +7,7 @@ export const GOOGLE_ADMIN_STRATEGY_NAME = 'google.admin.medusa-auth-plugin'; export type Profile = { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }; export type GoogleAuthOptions = { + type: 'google'; clientID: string; clientSecret: string; admin?: { @@ -30,7 +31,7 @@ export type GoogleAuthOptions = { accessToken: string, refreshToken: string, profile: Profile, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; @@ -56,7 +57,7 @@ export type GoogleAuthOptions = { accessToken: string, refreshToken: string, profile: Profile, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/__tests__/admin/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/__tests__/admin/verify-callback.spec.ts index d73b6c84..acfb922a 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/__tests__/admin/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/__tests__/admin/verify-callback.spec.ts @@ -1,9 +1,9 @@ import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AUTH_PROVIDER_KEY } from '../../../../types'; -import { LinkedinAdminStrategy } from '../../admin'; -import { LinkedinAuthOptions, LINKEDIN_ADMIN_STRATEGY_NAME, Profile } from '../../types'; +import { AUTH_PROVIDER_KEY, IStrategy } from '../../../../types'; +import { LINKEDIN_ADMIN_STRATEGY_NAME, LinkedinAuthOptions, Profile } from '../../types'; +import { getLinkedinAdminStrategy } from '../../admin'; -describe('Linkedin admin strategy verify callback', function () { +describe('Linkedin admin strategy verify callback', function() { const existsEmail = 'exists@test.fr'; const existsEmailWithProviderKey = 'exist3s@test.fr'; const existsEmailWithWrongProviderKey = 'exist4s@test.fr'; @@ -13,7 +13,7 @@ describe('Linkedin admin strategy verify callback', function () { let accessToken: string; let refreshToken: string; let profile: Profile; - let linkedinAdminStrategy: LinkedinAdminStrategy; + let linkedinAdminStrategy: IStrategy; beforeEach(() => { profile = { @@ -35,7 +35,7 @@ describe('Linkedin admin strategy verify callback', function () { return { id: 'test2', metadata: { - [AUTH_PROVIDER_KEY]: LINKEDIN_ADMIN_STRATEGY_NAME, + [AUTH_PROVIDER_KEY]: LINKEDIN_ADMIN_STRATEGY_NAME + '_test', }, }; } @@ -59,8 +59,9 @@ describe('Linkedin admin strategy verify callback', function () { } as MedusaContainer; }); - describe('when strict is set to admin', function () { + describe('when strict is set to admin', function() { beforeEach(() => { + const LinkedinAdminStrategy = getLinkedinAdminStrategy('test'); linkedinAdminStrategy = new LinkedinAdminStrategy( container, {} as ConfigModule, @@ -69,7 +70,7 @@ describe('Linkedin admin strategy verify callback', function () { clientSecret: 'fake', admin: {}, } as LinkedinAuthOptions, - 'admin' + 'admin', ); }); @@ -86,7 +87,7 @@ describe('Linkedin admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); }); @@ -124,8 +125,9 @@ describe('Linkedin admin strategy verify callback', function () { }); }); - describe('when strict is set to store', function () { + describe('when strict is set to store', function() { beforeEach(() => { + const LinkedinAdminStrategy = getLinkedinAdminStrategy('test'); linkedinAdminStrategy = new LinkedinAdminStrategy( container, {} as ConfigModule, @@ -134,7 +136,7 @@ describe('Linkedin admin strategy verify callback', function () { clientSecret: 'fake', admin: {}, } as LinkedinAuthOptions, - 'store' + 'store', ); }); @@ -151,7 +153,7 @@ describe('Linkedin admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); }); @@ -164,7 +166,7 @@ describe('Linkedin admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); }); @@ -177,7 +179,7 @@ describe('Linkedin admin strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); diff --git a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/__tests__/store/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/__tests__/store/verify-callback.spec.ts index 64dce4be..8ab4533b 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/__tests__/store/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/__tests__/store/verify-callback.spec.ts @@ -1,9 +1,9 @@ -import { LinkedinStoreStrategy } from '../../store'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { CUSTOMER_METADATA_KEY, AUTH_PROVIDER_KEY } from '../../../../types'; -import { LinkedinAuthOptions, LINKEDIN_STORE_STRATEGY_NAME, Profile } from '../../types'; +import { AUTH_PROVIDER_KEY, CUSTOMER_METADATA_KEY, IStrategy } from '../../../../types'; +import { LINKEDIN_STORE_STRATEGY_NAME, LinkedinAuthOptions, Profile } from '../../types'; +import { getLinkedinStoreStrategy } from '../../store'; -describe('Linkedin store strategy verify callback', function () { +describe('Linkedin store strategy verify callback', function() { const existsEmail = 'exists@test.fr'; const existsEmailWithMeta = 'exist2s@test.fr'; const existsEmailWithMetaAndProviderKey = 'exist3s@test.fr'; @@ -14,7 +14,7 @@ describe('Linkedin store strategy verify callback', function () { let accessToken: string; let refreshToken: string; let profile: Profile; - let linkedinStoreStrategy: LinkedinStoreStrategy; + let linkedinStoreStrategy: IStrategy; let updateFn; let createFn; @@ -34,12 +34,12 @@ describe('Linkedin store strategy verify callback', function () { resolve: (name: string): T => { const container_ = { manager: { - transaction: function (cb) { + transaction: function(cb) { return cb(); }, }, customerService: { - withTransaction: function () { + withTransaction: function() { return this; }, update: updateFn, @@ -65,7 +65,7 @@ describe('Linkedin store strategy verify callback', function () { id: 'test3', metadata: { [CUSTOMER_METADATA_KEY]: true, - [AUTH_PROVIDER_KEY]: LINKEDIN_STORE_STRATEGY_NAME, + [AUTH_PROVIDER_KEY]: LINKEDIN_STORE_STRATEGY_NAME + '_test', }, }; } @@ -90,8 +90,9 @@ describe('Linkedin store strategy verify callback', function () { } as MedusaContainer; }); - describe('when strict is set to store', function () { + describe('when strict is set to store', function() { beforeEach(() => { + const LinkedinStoreStrategy = getLinkedinStoreStrategy('test'); linkedinStoreStrategy = new LinkedinStoreStrategy( container, {} as ConfigModule, @@ -100,7 +101,7 @@ describe('Linkedin store strategy verify callback', function () { clientSecret: 'fake', store: {}, } as LinkedinAuthOptions, - 'store' + 'store', ); }); @@ -117,7 +118,7 @@ describe('Linkedin store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); @@ -141,7 +142,7 @@ describe('Linkedin store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); expect(updateFn).toHaveBeenCalledTimes(1); }); @@ -155,7 +156,7 @@ describe('Linkedin store strategy verify callback', function () { .validate(req, accessToken, refreshToken, profile) .catch((err) => err); expect(err).toEqual( - new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`) + new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`), ); }); @@ -172,14 +173,15 @@ describe('Linkedin store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); expect(createFn).toHaveBeenCalledTimes(1); }); }); - describe('when strict is set to admn', function () { + describe('when strict is set to admn', function() { beforeEach(() => { + const LinkedinStoreStrategy = getLinkedinStoreStrategy('test'); linkedinStoreStrategy = new LinkedinStoreStrategy( container, {} as ConfigModule, @@ -188,7 +190,7 @@ describe('Linkedin store strategy verify callback', function () { clientSecret: 'fake', store: {}, } as LinkedinAuthOptions, - 'admin' + 'admin', ); }); @@ -205,7 +207,7 @@ describe('Linkedin store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test3', - }) + }), ); }); @@ -218,7 +220,7 @@ describe('Linkedin store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); }); @@ -231,7 +233,7 @@ describe('Linkedin store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test2', - }) + }), ); expect(updateFn).toHaveBeenCalledTimes(1); }); @@ -245,7 +247,7 @@ describe('Linkedin store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test4', - }) + }), ); }); @@ -262,7 +264,7 @@ describe('Linkedin store strategy verify callback', function () { expect(data).toEqual( expect.objectContaining({ id: 'test', - }) + }), ); expect(createFn).toHaveBeenCalledTimes(1); }); diff --git a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/admin.ts b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/admin.ts index 4c1f77e7..0b5beeed 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/admin.ts @@ -1,67 +1,77 @@ -import { Strategy as LinkedinStrategy } from 'passport-linkedin-oauth2'; +import { Strategy as LinkedinStrategy, StrategyOptionWithRequest } from 'passport-linkedin-oauth2'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; import { Router } from 'express'; import { LINKEDIN_ADMIN_STRATEGY_NAME, LinkedinAuthOptions, Profile } from './types'; import { PassportStrategy } from '../../core/passport/Strategy'; import { validateAdminCallback } from '../../core/validate-callback'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class LinkedinAdminStrategy extends PassportStrategy(LinkedinStrategy, LINKEDIN_ADMIN_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: LinkedinAuthOptions, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - clientID: strategyOptions.clientID, - clientSecret: strategyOptions.clientSecret, - callbackURL: strategyOptions.admin.callbackUrl, - passReqToCallback: true, - scope: ['r_emailaddress'], - state: true, - }); - } - - async validate( - req: Request, - accessToken: string, - refreshToken: string, - profile: Profile - ): Promise { - if (this.strategyOptions.admin.verifyCallback) { - return await this.strategyOptions.admin.verifyCallback( - this.container, - req, - accessToken, - refreshToken, - profile, - this.strict - ); +export function getLinkedinAdminStrategy(id: string): StrategyFactory { + const strategyName = `${LINKEDIN_ADMIN_STRATEGY_NAME}_${id}`; + return class extends PassportStrategy(LinkedinStrategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: LinkedinAuthOptions, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + clientID: strategyOptions.clientID, + clientSecret: strategyOptions.clientSecret, + callbackURL: strategyOptions.admin.callbackUrl, + passReqToCallback: true, + scope: ['r_emailaddress'], + state: true, + } as StrategyOptionWithRequest); } - return await validateAdminCallback(profile, { - container: this.container, - strategyErrorIdentifier: 'linkedin', - strict: this.strict, - }); - } + async validate( + req: Request, + accessToken: string, + refreshToken: string, + profile: Profile, + ): Promise { + if (this.strategyOptions.admin.verifyCallback) { + return await this.strategyOptions.admin.verifyCallback( + this.container, + req, + accessToken, + refreshToken, + profile, + this.strict, + ); + } + + return await validateAdminCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'linkedin', + strict: this.strict, + strategyName, + }); + } + }; } /** * Return the router that hold the linkedin admin authentication routes + * @param id * @param linkedin * @param configModule */ -export function getLinkedinAdminAuthRouter(linkedin: LinkedinAuthOptions, configModule: ConfigModule): Router { +export function getLinkedinAdminAuthRouter( + id: string, + linkedin: LinkedinAuthOptions, + configModule: ConfigModule, +): Router { + const strategyName = `${LINKEDIN_ADMIN_STRATEGY_NAME}_${id}`; return passportAuthRoutesBuilder({ domain: 'admin', configModule, authPath: linkedin.admin.authPath ?? '/admin/auth/linkedin', authCallbackPath: linkedin.admin.authCallbackPath ?? '/admin/auth/linkedin/cb', successRedirect: linkedin.admin.successRedirect, - strategyName: LINKEDIN_ADMIN_STRATEGY_NAME, + strategyName, passportAuthenticateMiddlewareOptions: { scope: [ 'https://www.linkedinapis.com/auth/userinfo.email', diff --git a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/index.ts b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/index.ts index 3ef91c74..77ff775c 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/index.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/index.ts @@ -1,11 +1,11 @@ -import { AuthOptions, StrategyExport } from '../../types'; +import { StrategyExport } from '../../types'; import { Router } from 'express'; -import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; import { getLinkedinAdminAuthRouter, + getLinkedinAdminStrategy, getLinkedinStoreAuthRouter, - LinkedinAdminStrategy, - LinkedinStoreStrategy, + getLinkedinStoreStrategy, + LinkedinAuthOptions, } from '../linkedin'; export * from './types'; @@ -13,26 +13,30 @@ export * from './admin'; export * from './store'; export default { - load: (container: MedusaContainer, configModule: ConfigModule, options: AuthOptions): void => { - if (options.linkedin?.admin) { - new LinkedinAdminStrategy(container, configModule, options.linkedin, options.strict); + load: (container, configModule, options): void => { + const id = options.identifier ?? options.type; + if (options.admin) { + const Clazz = getLinkedinAdminStrategy(id); + new Clazz(container, configModule, options, options.strict); } - if (options.linkedin?.store) { - new LinkedinStoreStrategy(container, configModule, options.linkedin, options.strict); + if (options.store) { + const Clazz = getLinkedinStoreStrategy(id); + new Clazz(container, configModule, options, options.strict); } }, - getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { + getRouter: (configModule, options): Router[] => { + const id = options.identifier ?? options.type; const routers = []; - if (options.linkedin?.admin) { - routers.push(getLinkedinAdminAuthRouter(options.linkedin, configModule)); + if (options.admin) { + routers.push(getLinkedinAdminAuthRouter(id, options, configModule)); } - if (options.linkedin?.store) { - routers.push(getLinkedinStoreAuthRouter(options.linkedin, configModule)); + if (options.store) { + routers.push(getLinkedinStoreAuthRouter(id, options, configModule)); } return routers; }, -} as StrategyExport; +} as StrategyExport; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/store.ts b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/store.ts index ac772191..59c7b0e6 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/store.ts @@ -1,67 +1,77 @@ import { Router } from 'express'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { Strategy as LinkedinStrategy } from 'passport-linkedin-oauth2'; +import { Strategy as LinkedinStrategy, StrategyOptionWithRequest } from 'passport-linkedin-oauth2'; import { PassportStrategy } from '../../core/passport/Strategy'; import { LINKEDIN_STORE_STRATEGY_NAME, LinkedinAuthOptions, Profile } from './types'; import { validateStoreCallback } from '../../core/validate-callback'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class LinkedinStoreStrategy extends PassportStrategy(LinkedinStrategy, LINKEDIN_STORE_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: LinkedinAuthOptions, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - clientID: strategyOptions.clientID, - clientSecret: strategyOptions.clientSecret, - callbackURL: strategyOptions.store.callbackUrl, - passReqToCallback: true, - scope: ['r_emailaddress'], - state: true, - }); - } - - async validate( - req: Request, - accessToken: string, - refreshToken: string, - profile: Profile - ): Promise { - if (this.strategyOptions.store.verifyCallback) { - return await this.strategyOptions.store.verifyCallback( - this.container, - req, - accessToken, - refreshToken, - profile, - this.strict - ); +export function getLinkedinStoreStrategy(id: string): StrategyFactory { + const strategyName = `${LINKEDIN_STORE_STRATEGY_NAME}_${id}`; + return class LinkedinStoreStrategy extends PassportStrategy(LinkedinStrategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: LinkedinAuthOptions, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + clientID: strategyOptions.clientID, + clientSecret: strategyOptions.clientSecret, + callbackURL: strategyOptions.store.callbackUrl, + passReqToCallback: true, + scope: ['r_emailaddress'], + state: true, + } as StrategyOptionWithRequest); } - return await validateStoreCallback(profile, { - container: this.container, - strategyErrorIdentifier: 'linkedin', - strict: this.strict, - }); - } + async validate( + req: Request, + accessToken: string, + refreshToken: string, + profile: Profile, + ): Promise { + if (this.strategyOptions.store.verifyCallback) { + return await this.strategyOptions.store.verifyCallback( + this.container, + req, + accessToken, + refreshToken, + profile, + this.strict, + ); + } + + return await validateStoreCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'linkedin', + strict: this.strict, + strategyName, + }); + } + }; } /** * Return the router that hold the linkedin store authentication routes + * @param id * @param linkedin * @param configModule */ -export function getLinkedinStoreAuthRouter(linkedin: LinkedinAuthOptions, configModule: ConfigModule): Router { +export function getLinkedinStoreAuthRouter( + id: string, + linkedin: LinkedinAuthOptions, + configModule: ConfigModule, +): Router { + const strategyName = `${LINKEDIN_STORE_STRATEGY_NAME}_${id}`; return passportAuthRoutesBuilder({ domain: 'store', configModule, authPath: linkedin.store.authPath ?? '/store/auth/linkedin', authCallbackPath: linkedin.store.authCallbackPath ?? '/store/auth/linkedin/cb', successRedirect: linkedin.store.successRedirect, - strategyName: LINKEDIN_STORE_STRATEGY_NAME, + strategyName, passportAuthenticateMiddlewareOptions: { scope: [ 'https://www.linkedinapis.com/auth/userinfo.email', diff --git a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/types.ts b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/types.ts index bdc5b720..8a402921 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/types.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/types.ts @@ -1,5 +1,5 @@ import { MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AuthOptions } from '../../types'; +import { AuthProvider } from '../../types'; export const LINKEDIN_ADMIN_STRATEGY_NAME = 'linkedin.admin.medusa-auth-plugin'; export const LINKEDIN_STORE_STRATEGY_NAME = 'linkedin.store.medusa-auth-plugin'; @@ -7,6 +7,7 @@ export const LINKEDIN_STORE_STRATEGY_NAME = 'linkedin.store.medusa-auth-plugin'; export type Profile = { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }; export type LinkedinAuthOptions = { + type: 'linkedin'; clientID: string; clientSecret: string; admin?: { @@ -30,7 +31,7 @@ export type LinkedinAuthOptions = { accessToken: string, refreshToken: string, profile: Profile, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; @@ -56,7 +57,7 @@ export type LinkedinAuthOptions = { accessToken: string, refreshToken: string, profile: Profile, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/admin/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/admin/verify-callback.spec.ts index 42dc574f..de65b760 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/admin/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/admin/verify-callback.spec.ts @@ -1,7 +1,7 @@ import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { OAuth2AdminStrategy } from '../../admin'; -import { AUTH_PROVIDER_KEY } from '../../../../types'; +import { AUTH_PROVIDER_KEY, IStrategy } from '../../../../types'; import { OAUTH2_ADMIN_STRATEGY_NAME, OAuth2AuthOptions } from '../../types'; +import { getOAuth2AdminStrategy } from '../../admin'; describe('OAuth2 admin strategy verify callback', function() { const existsEmail = 'exists@test.fr'; @@ -13,7 +13,7 @@ describe('OAuth2 admin strategy verify callback', function() { let accessToken: string; let refreshToken: string; let profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }; - let oauth2AdminStrategy: OAuth2AdminStrategy; + let oauth2AdminStrategy: IStrategy; beforeEach(() => { profile = { @@ -35,7 +35,7 @@ describe('OAuth2 admin strategy verify callback', function() { return { id: 'test2', metadata: { - [AUTH_PROVIDER_KEY]: OAUTH2_ADMIN_STRATEGY_NAME, + [AUTH_PROVIDER_KEY]: OAUTH2_ADMIN_STRATEGY_NAME + '_test', }, }; } @@ -61,6 +61,7 @@ describe('OAuth2 admin strategy verify callback', function() { describe('when strict is set to admin', function() { beforeEach(() => { + const OAuth2AdminStrategy = getOAuth2AdminStrategy('test'); oauth2AdminStrategy = new OAuth2AdminStrategy( container, {} as ConfigModule, @@ -122,6 +123,7 @@ describe('OAuth2 admin strategy verify callback', function() { describe('when strict is set for store only', function() { beforeEach(() => { + const OAuth2AdminStrategy = getOAuth2AdminStrategy('test'); oauth2AdminStrategy = new OAuth2AdminStrategy( container, {} as ConfigModule, diff --git a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/store/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/store/verify-callback.spec.ts index 13ff23c5..3ac9fb7e 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/store/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/store/verify-callback.spec.ts @@ -1,7 +1,7 @@ -import { OAuth2StoreStrategy } from '../../store'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AUTH_PROVIDER_KEY, CUSTOMER_METADATA_KEY } from '../../../../types'; +import { AUTH_PROVIDER_KEY, CUSTOMER_METADATA_KEY, IStrategy } from '../../../../types'; import { OAUTH2_STORE_STRATEGY_NAME, OAuth2AuthOptions, Profile } from '../../types'; +import { getOAuth2StoreStrategy } from '../../store'; describe('OAuth2 store strategy verify callback', function() { const existsEmail = 'exists@test.fr'; @@ -14,7 +14,7 @@ describe('OAuth2 store strategy verify callback', function() { let accessToken: string; let refreshToken: string; let profile: Profile; - let oauth2StoreStrategy: OAuth2StoreStrategy; + let oauth2StoreStrategy: IStrategy; let updateFn; let createFn; @@ -65,7 +65,7 @@ describe('OAuth2 store strategy verify callback', function() { id: 'test3', metadata: { [CUSTOMER_METADATA_KEY]: true, - [AUTH_PROVIDER_KEY]: OAUTH2_STORE_STRATEGY_NAME, + [AUTH_PROVIDER_KEY]: OAUTH2_STORE_STRATEGY_NAME + '_test', }, }; } @@ -92,6 +92,7 @@ describe('OAuth2 store strategy verify callback', function() { describe('when strict is set to store', function() { beforeEach(() => { + const OAuth2StoreStrategy = getOAuth2StoreStrategy('test'); oauth2StoreStrategy = new OAuth2StoreStrategy( container, {} as ConfigModule, @@ -178,6 +179,7 @@ describe('OAuth2 store strategy verify callback', function() { describe('when strict is set to admin only', function() { beforeEach(() => { + const OAuth2StoreStrategy = getOAuth2StoreStrategy('test'); oauth2StoreStrategy = new OAuth2StoreStrategy( container, {} as ConfigModule, diff --git a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/admin.ts b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/admin.ts index 989b8d6a..39a74abf 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/admin.ts @@ -1,69 +1,74 @@ -import {Strategy as OAuth2Strategy, StrategyOptionsWithRequest} from 'passport-oauth2'; +import { Strategy as OAuth2Strategy, StrategyOptionsWithRequest } from 'passport-oauth2'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; import { Router } from 'express'; import { OAUTH2_ADMIN_STRATEGY_NAME, OAuth2AuthOptions, Profile } from './types'; import { PassportStrategy } from '../../core/passport/Strategy'; import { validateAdminCallback } from '../../core/validate-callback'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class OAuth2AdminStrategy extends PassportStrategy(OAuth2Strategy, OAUTH2_ADMIN_STRATEGY_NAME) { +export function getOAuth2AdminStrategy(id: string): StrategyFactory { + const strategyName = `${OAUTH2_ADMIN_STRATEGY_NAME}_${id}`; + return class extends PassportStrategy(OAuth2Strategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: OAuth2AuthOptions, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + authorizationURL: strategyOptions.authorizationURL, + tokenURL: strategyOptions.tokenURL, + clientID: strategyOptions.clientID, + clientSecret: strategyOptions.clientSecret, + callbackURL: strategyOptions.admin.callbackUrl, + passReqToCallback: true, + scope: strategyOptions.scope, + } as StrategyOptionsWithRequest); + } - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: OAuth2AuthOptions, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - authorizationURL: strategyOptions.authorizationURL, - tokenURL: strategyOptions.tokenURL, - clientID: strategyOptions.clientID, - clientSecret: strategyOptions.clientSecret, - callbackURL: strategyOptions.admin.callbackUrl, - passReqToCallback: true, - scope: strategyOptions.scope, - } as StrategyOptionsWithRequest); - } + async validate( + req: Request, + accessToken: string, + refreshToken: string, + profile: Profile, + ): Promise { + if (this.strategyOptions.admin.verifyCallback) { + return await this.strategyOptions.admin.verifyCallback( + this.container, + req, + accessToken, + refreshToken, + profile, + this.strict, + ); + } - async validate( - req: Request, - accessToken: string, - refreshToken: string, - profile: Profile - ): Promise { - if (this.strategyOptions.admin.verifyCallback) { - return await this.strategyOptions.admin.verifyCallback( - this.container, - req, - accessToken, - refreshToken, - profile, - this.strict - ); + return await validateAdminCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'oauth2', + strict: this.strict, + strategyName, + }); } - - return await validateAdminCallback(profile, { - container: this.container, - strategyErrorIdentifier: 'oauth2', - strict: this.strict, - }); - } + }; } /** * Return the router that hold the oauth2 admin authentication routes + * @param id * @param oauth2 * @param configModule */ -export function getOAuth2AdminAuthRouter(oauth2: OAuth2AuthOptions, configModule: ConfigModule): Router { +export function getOAuth2AdminAuthRouter(id: string, oauth2: OAuth2AuthOptions, configModule: ConfigModule): Router { + const strategyName = `${OAUTH2_ADMIN_STRATEGY_NAME}_${id}`; return passportAuthRoutesBuilder({ domain: 'admin', configModule, authPath: oauth2.admin.authPath ?? '/admin/auth/oauth2', authCallbackPath: oauth2.admin.authCallbackPath ?? '/admin/auth/oauth2/cb', successRedirect: oauth2.admin.successRedirect, - strategyName: OAUTH2_ADMIN_STRATEGY_NAME, + strategyName, passportAuthenticateMiddlewareOptions: {}, passportCallbackAuthenticateMiddlewareOptions: { failureRedirect: oauth2.admin.failureRedirect, diff --git a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/index.ts b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/index.ts index 36077a45..3219898a 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/index.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/index.ts @@ -1,34 +1,38 @@ -import { AuthOptions, StrategyExport } from '../../types'; +import { StrategyExport } from '../../types'; import { Router } from 'express'; -import { getOAuth2AdminAuthRouter, OAuth2AdminStrategy } from './admin'; -import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { getOAuth2StoreAuthRouter, OAuth2StoreStrategy } from './store'; +import { getOAuth2AdminAuthRouter, getOAuth2AdminStrategy } from './admin'; +import { getOAuth2StoreAuthRouter, getOAuth2StoreStrategy } from './store'; +import { OAuth2AuthOptions } from './types'; export * from './types'; export * from './admin'; export * from './store'; export default { - load: (container: MedusaContainer, configModule: ConfigModule, options: AuthOptions): void => { - if (options.oauth2?.admin) { - new OAuth2AdminStrategy(container, configModule, options.oauth2, options.strict); + load: (container, configModule, options): void => { + const id = options.identifier ?? options.type; + if (options.admin) { + const Clazz = getOAuth2AdminStrategy(id); + new Clazz(container, configModule, options, options.strict); } - if (options.oauth2?.store) { - new OAuth2StoreStrategy(container, configModule, options.oauth2, options.strict); + if (options.store) { + const Clazz = getOAuth2StoreStrategy(id); + new Clazz(container, configModule, options, options.strict); } }, - getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { + getRouter: (configModule, options): Router[] => { + const id = options.identifier ?? options.type; const routers = []; - if (options.oauth2?.admin) { - routers.push(getOAuth2AdminAuthRouter(options.oauth2, configModule)); + if (options.admin) { + routers.push(getOAuth2AdminAuthRouter(id, options, configModule)); } - if (options.oauth2?.store) { - routers.push(getOAuth2StoreAuthRouter(options.oauth2, configModule)); + if (options.store) { + routers.push(getOAuth2StoreAuthRouter(id, options, configModule)); } return routers; }, -} as StrategyExport; +} as StrategyExport; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/store.ts b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/store.ts index 4216a684..698cce81 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/store.ts @@ -1,68 +1,74 @@ import { Router } from 'express'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import {Strategy as OAuth2Strategy, StrategyOptionsWithRequest} from 'passport-oauth2'; +import { Strategy as OAuth2Strategy, StrategyOptionsWithRequest } from 'passport-oauth2'; import { PassportStrategy } from '../../core/passport/Strategy'; import { OAUTH2_STORE_STRATEGY_NAME, OAuth2AuthOptions, Profile } from './types'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; import { validateStoreCallback } from '../../core/validate-callback'; -import { AuthOptions } from '../../types'; +import { AuthProvider, StrategyFactory } from '../../types'; -export class OAuth2StoreStrategy extends PassportStrategy(OAuth2Strategy, OAUTH2_STORE_STRATEGY_NAME) { - constructor( - protected readonly container: MedusaContainer, - protected readonly configModule: ConfigModule, - protected readonly strategyOptions: OAuth2AuthOptions, - protected readonly strict?: AuthOptions['strict'] - ) { - super({ - authorizationURL: strategyOptions.authorizationURL, - tokenURL: strategyOptions.tokenURL, - clientID: strategyOptions.clientID, - clientSecret: strategyOptions.clientSecret, - callbackURL: strategyOptions.store.callbackUrl, - passReqToCallback: true, - scope: strategyOptions.scope, - } as StrategyOptionsWithRequest); - } - - async validate( - req: Request, - accessToken: string, - refreshToken: string, - profile: Profile - ): Promise { - if (this.strategyOptions.store.verifyCallback) { - return await this.strategyOptions.store.verifyCallback( - this.container, - req, - accessToken, - refreshToken, - profile, - this.strict - ); +export function getOAuth2StoreStrategy(id: string): StrategyFactory { + const strategyName = `${OAUTH2_STORE_STRATEGY_NAME}_${id}`; + return class extends PassportStrategy(OAuth2Strategy, strategyName) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: OAuth2AuthOptions, + protected readonly strict?: AuthProvider['strict'], + ) { + super({ + authorizationURL: strategyOptions.authorizationURL, + tokenURL: strategyOptions.tokenURL, + clientID: strategyOptions.clientID, + clientSecret: strategyOptions.clientSecret, + callbackURL: strategyOptions.store.callbackUrl, + passReqToCallback: true, + scope: strategyOptions.scope, + } as StrategyOptionsWithRequest); } - return await validateStoreCallback(profile, { - container: this.container, - strategyErrorIdentifier: 'oauth2', - strict: this.strict, - }); - } + async validate( + req: Request, + accessToken: string, + refreshToken: string, + profile: Profile, + ): Promise { + if (this.strategyOptions.store.verifyCallback) { + return await this.strategyOptions.store.verifyCallback( + this.container, + req, + accessToken, + refreshToken, + profile, + this.strict, + ); + } + + return await validateStoreCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'oauth2', + strict: this.strict, + strategyName, + }); + } + }; } /** * Return the router that hold the oauth2 store authentication routes + * @param id * @param oauth2 * @param configModule */ -export function getOAuth2StoreAuthRouter(oauth2: OAuth2AuthOptions, configModule: ConfigModule): Router { +export function getOAuth2StoreAuthRouter(id: string, oauth2: OAuth2AuthOptions, configModule: ConfigModule): Router { + const strategyName = `${OAUTH2_STORE_STRATEGY_NAME}_${id}`; return passportAuthRoutesBuilder({ domain: 'store', configModule, authPath: oauth2.store.authPath ?? '/store/auth/oauth2', authCallbackPath: oauth2.store.authCallbackPath ?? '/store/auth/oauth2/cb', successRedirect: oauth2.store.successRedirect, - strategyName: OAUTH2_STORE_STRATEGY_NAME, + strategyName, passportAuthenticateMiddlewareOptions: {}, passportCallbackAuthenticateMiddlewareOptions: { failureRedirect: oauth2.store.failureRedirect, diff --git a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/types.ts b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/types.ts index ad5d50b8..841e30b2 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/types.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/types.ts @@ -1,5 +1,5 @@ import { MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AuthOptions } from '../../types'; +import { AuthProvider } from '../../types'; export const OAUTH2_STORE_STRATEGY_NAME = 'oauth2.store.medusa-auth-plugin'; export const OAUTH2_ADMIN_STRATEGY_NAME = 'oauth2.admin.medusa-auth-plugin'; @@ -7,6 +7,7 @@ export const OAUTH2_ADMIN_STRATEGY_NAME = 'oauth2.admin.medusa-auth-plugin'; export type Profile = { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }; export type OAuth2AuthOptions = { + type: 'oauth2'; authorizationURL: string; tokenURL: string; clientID: string; @@ -32,7 +33,7 @@ export type OAuth2AuthOptions = { accessToken: string, refreshToken: string, profile: Profile, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; @@ -58,7 +59,7 @@ export type OAuth2AuthOptions = { accessToken: string, refreshToken: string, profile: Profile, - strict?: AuthOptions['strict'] + strict?: AuthProvider['strict'], ) => Promise; expiresIn?: number; diff --git a/packages/medusa-plugin-auth/src/core/passport/Strategy.ts b/packages/medusa-plugin-auth/src/core/passport/Strategy.ts index bc57cf01..6054578c 100644 --- a/packages/medusa-plugin-auth/src/core/passport/Strategy.ts +++ b/packages/medusa-plugin-auth/src/core/passport/Strategy.ts @@ -4,13 +4,11 @@ type Type = new (...args: any[]) => T; export function PassportStrategy = any>( Strategy: T, - name?: string | undefined + name?: string | undefined, ): { - new (...args): InstanceType; + new(...args): InstanceType; } { abstract class MixinStrategy extends Strategy { - abstract validate(...args: any[]): any; - protected constructor(...args: any[]) { const callback = async (...params: any[]) => { const done = params.pop(); @@ -33,9 +31,12 @@ export function PassportStrategy = any>( } } + abstract validate(...args: any[]): any; + getPassportInstance() { return passport; } } + return MixinStrategy; } diff --git a/packages/medusa-plugin-auth/src/core/passport/utils/auth-routes-builder.ts b/packages/medusa-plugin-auth/src/core/passport/utils/auth-routes-builder.ts index 107a01f4..e83ff567 100644 --- a/packages/medusa-plugin-auth/src/core/passport/utils/auth-routes-builder.ts +++ b/packages/medusa-plugin-auth/src/core/passport/utils/auth-routes-builder.ts @@ -27,16 +27,16 @@ type PassportCallbackAuthenticateMiddlewareOptions = { * @param successRedirect */ export function passportAuthRoutesBuilder({ - domain, - configModule, - authPath, - strategyName, - passportAuthenticateMiddlewareOptions, - passportCallbackAuthenticateMiddlewareOptions, - successRedirect, - authCallbackPath, - expiresIn, -}: { + domain, + configModule, + authPath, + strategyName, + passportAuthenticateMiddlewareOptions, + passportCallbackAuthenticateMiddlewareOptions, + successRedirect, + authCallbackPath, + expiresIn, + }: { domain: 'admin' | 'store'; configModule: ConfigModule; authPath: string; @@ -73,7 +73,7 @@ export function passportAuthRoutesBuilder({ passport.authenticate(strategyName, { ...passportAuthenticateMiddlewareOptions, session: false, - }) + }), ); const callbackHandler = authCallbackMiddleware((req, res) => successAction(req, res)); @@ -88,7 +88,7 @@ export function passportAuthRoutesBuilder({ next(); }, - function (req, res, next) { + function(req, res, next) { passport.authenticate( strategyName, Object.assign({}, passportCallbackAuthenticateMiddlewareOptions, { @@ -100,8 +100,8 @@ export function passportAuthRoutesBuilder({ if (passportCallbackAuthenticateMiddlewareOptions?.failureRedirect) { return res.redirect( passportCallbackAuthenticateMiddlewareOptions.failureRedirect + - '?message=' + - options.msg + '?message=' + + options.msg, ); } else { return res.status(401).json({ message: options.msg }); @@ -109,9 +109,9 @@ export function passportAuthRoutesBuilder({ } req.user ??= user; return callbackHandler(req, res); - } + }, )(req, res, next); - } + }, ); return router; @@ -122,7 +122,7 @@ function successActionHandlerFactory( domain: 'admin' | 'store', configModule: ConfigModule, defaultRedirect: string, - expiresIn?: number + expiresIn?: number, ) { const returnAccessToken = req.query.returnAccessToken == 'true'; const redirectUrl = (req.query.redirectTo ? req.query.redirectTo : defaultRedirect) as string; diff --git a/packages/medusa-plugin-auth/src/core/validate-callback.ts b/packages/medusa-plugin-auth/src/core/validate-callback.ts index 0e97d40c..e4b03ea4 100644 --- a/packages/medusa-plugin-auth/src/core/validate-callback.ts +++ b/packages/medusa-plugin-auth/src/core/validate-callback.ts @@ -5,20 +5,20 @@ import { MedusaError } from 'medusa-core-utils'; import { EntityManager } from 'typeorm'; import { AUTH_PROVIDER_KEY, - AuthOptions, + AuthProvider, CUSTOMER_METADATA_KEY, EMAIL_VERIFIED_KEY, StrategyErrorIdentifierType, - strategyNames, } from '../types'; /** * Default validate callback used by an admin passport strategy * - * @param profile - * @param container - * @param strategyErrorIdentifier - * @param strict + * @param profile The profile returned by the passport strategy + * @param container The medusa container + * @param strategyErrorIdentifier It will be used to compose the error message in case of an error (e.g Google, Facebook) + * @param strict If strict is set to true, it will check if the user already exists in the database + * @param strategyName The name of the strategy */ export async function validateAdminCallback< T extends { emails?: { value: string }[] } = { @@ -30,11 +30,13 @@ export async function validateAdminCallback< container, strategyErrorIdentifier, strict, + strategyName, }: { container: MedusaContainer; strategyErrorIdentifier: StrategyErrorIdentifierType; - strict?: AuthOptions['strict']; - } + strict?: AuthProvider['strict']; + strategyName: string; + }, ): Promise<{ id: string } | never> { const userService: UserService = container.resolve('userService'); const email = profile.emails?.[0]?.value; @@ -42,7 +44,7 @@ export async function validateAdminCallback< if (!email) { throw new MedusaError( MedusaError.Types.NOT_ALLOWED, - `Your ${capitalize(strategyErrorIdentifier)} account does not contains any email and cannot be used` + `Your ${capitalize(strategyErrorIdentifier)} account does not contains any email and cannot be used`, ); } @@ -52,7 +54,7 @@ export async function validateAdminCallback< strict ??= 'all'; if ( (strict === 'all' || strict === 'admin') && - (!user.metadata || user.metadata[AUTH_PROVIDER_KEY] !== strategyNames[strategyErrorIdentifier].admin) + (!user.metadata || user.metadata[AUTH_PROVIDER_KEY] !== strategyName) ) { throw new MedusaError(MedusaError.Types.INVALID_DATA, `Admin with email ${email} already exists`); } @@ -70,6 +72,7 @@ export async function validateAdminCallback< * @param strategyErrorIdentifier It will be used to compose the error message in case of an error (e.g Google, Facebook) * @param container * @param strict + * @param strategyName */ export async function validateStoreCallback< T extends { @@ -87,11 +90,13 @@ export async function validateStoreCallback< container, strategyErrorIdentifier, strict, + strategyName, }: { container: MedusaContainer; strategyErrorIdentifier: StrategyErrorIdentifierType; - strict?: AuthOptions['strict']; - } + strategyName: string; + strict?: AuthProvider['strict']; + }, ): Promise<{ id: string } | never> { const manager: EntityManager = container.resolve('manager'); const customerService: CustomerService = container.resolve('customerService'); @@ -103,7 +108,7 @@ export async function validateStoreCallback< if (!email) { throw new MedusaError( MedusaError.Types.NOT_ALLOWED, - `Your ${capitalize(strategyErrorIdentifier)} account does not contains any email and cannot be used` + `Your ${capitalize(strategyErrorIdentifier)} account does not contains any email and cannot be used`, ); } @@ -119,7 +124,7 @@ export async function validateStoreCallback< customer.metadata[CUSTOMER_METADATA_KEY] && !customer.metadata[AUTH_PROVIDER_KEY] ) { - customer.metadata[AUTH_PROVIDER_KEY] = strategyNames[strategyErrorIdentifier].store; + customer.metadata[AUTH_PROVIDER_KEY] = strategyName; await customerService.withTransaction(transactionManager).update(customer.id, { metadata: customer.metadata, }); @@ -142,7 +147,7 @@ export async function validateStoreCallback< (strict === 'all' || strict === 'store') && (!customer.metadata || !customer.metadata[CUSTOMER_METADATA_KEY] || - customer.metadata[AUTH_PROVIDER_KEY] !== strategyNames[strategyErrorIdentifier].store) + customer.metadata[AUTH_PROVIDER_KEY] !== strategyName) ) { throw new MedusaError(MedusaError.Types.INVALID_DATA, `Customer with email ${email} already exists`); } else { @@ -161,7 +166,7 @@ export async function validateStoreCallback< email, metadata: { [CUSTOMER_METADATA_KEY]: true, - [AUTH_PROVIDER_KEY]: strategyNames[strategyErrorIdentifier].store, + [AUTH_PROVIDER_KEY]: strategyName, [EMAIL_VERIFIED_KEY]: hasEmailVerifiedField ? profile._json.email_verified : false, }, first_name: profile.name?.givenName ?? '', diff --git a/packages/medusa-plugin-auth/src/loaders/index.ts b/packages/medusa-plugin-auth/src/loaders/index.ts index a8d3ebe9..f65effcc 100644 --- a/packages/medusa-plugin-auth/src/loaders/index.ts +++ b/packages/medusa-plugin-auth/src/loaders/index.ts @@ -1,6 +1,6 @@ import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { AuthOptions } from '../types'; +import { AuthOptionsWrapper, handleOption } from '../types'; import OAuth2Strategy from '../auth-strategies/oauth2'; import GoogleStrategy from '../auth-strategies/google'; import FacebookStrategy from '../auth-strategies/facebook'; @@ -9,14 +9,44 @@ import FireaseStrategy from '../auth-strategies/firebase'; import Auth0Strategy from '../auth-strategies/auth0'; import AzureStrategy from '../auth-strategies/azure-oidc'; -export default async function authStrategiesLoader(container: MedusaContainer, authOptions: AuthOptions) { +export default async function authStrategiesLoader( + container: MedusaContainer, + authOptions: AuthOptionsWrapper | AuthOptionsWrapper[], +) { const configModule = container.resolve('configModule') as ConfigModule; + if (Array.isArray(authOptions)) { + for (const opt of authOptions) { + await handleStrategyLoading(opt, configModule, container); + } + } else { + await handleStrategyLoading(authOptions, configModule, container); + } +} + +async function handleStrategyLoading(opt: AuthOptionsWrapper, configModule: ConfigModule, container: MedusaContainer) { + const option = await handleOption(opt, configModule, container); - OAuth2Strategy.load(container, configModule, authOptions); - GoogleStrategy.load(container, configModule, authOptions); - FacebookStrategy.load(container, configModule, authOptions); - LinkedinStrategy.load(container, configModule, authOptions); - FireaseStrategy.load(container, configModule, authOptions); - Auth0Strategy.load(container, configModule, authOptions); - AzureStrategy.load(container, configModule, authOptions); + switch (option.type) { + case 'azure_oidc': + AzureStrategy.load(container, configModule, option); + break; + case 'google': + GoogleStrategy.load(container, configModule, option); + break; + case 'facebook': + FacebookStrategy.load(container, configModule, option); + break; + case 'linkedin': + LinkedinStrategy.load(container, configModule, option); + break; + case 'firebase': + FireaseStrategy.load(container, configModule, option); + break; + case 'auth0': + Auth0Strategy.load(container, configModule, option); + break; + case 'oauth2': + OAuth2Strategy.load(container, configModule, option); + break; + } } diff --git a/packages/medusa-plugin-auth/src/types/index.ts b/packages/medusa-plugin-auth/src/types/index.ts index bfd73111..f61b953f 100644 --- a/packages/medusa-plugin-auth/src/types/index.ts +++ b/packages/medusa-plugin-auth/src/types/index.ts @@ -1,40 +1,53 @@ import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; import { Router } from 'express'; -import { - FIREBASE_ADMIN_STRATEGY_NAME, - FIREBASE_STORE_STRATEGY_NAME, - FirebaseAuthOptions, -} from '../auth-strategies/firebase'; -import { GOOGLE_ADMIN_STRATEGY_NAME, GOOGLE_STORE_STRATEGY_NAME, GoogleAuthOptions } from '../auth-strategies/google'; -import { - FACEBOOK_ADMIN_STRATEGY_NAME, - FACEBOOK_STORE_STRATEGY_NAME, - FacebookAuthOptions, -} from '../auth-strategies/facebook'; -import { - LINKEDIN_ADMIN_STRATEGY_NAME, - LINKEDIN_STORE_STRATEGY_NAME, - LinkedinAuthOptions, -} from '../auth-strategies/linkedin'; -import { AUTH0_ADMIN_STRATEGY_NAME, AUTH0_STORE_STRATEGY_NAME, Auth0Options } from '../auth-strategies/auth0'; -import { AZURE_ADMIN_STRATEGY_NAME, AZURE_STORE_STRATEGY_NAME, AzureAuthOptions } from '../auth-strategies/azure-oidc'; -import { OAUTH2_ADMIN_STRATEGY_NAME, OAUTH2_STORE_STRATEGY_NAME, OAuth2AuthOptions } from '../auth-strategies/oauth2'; +import { FirebaseAuthOptions } from '../auth-strategies/firebase'; +import { GoogleAuthOptions } from '../auth-strategies/google'; +import { FacebookAuthOptions } from '../auth-strategies/facebook'; +import { LinkedinAuthOptions } from '../auth-strategies/linkedin'; +import { Auth0Options } from '../auth-strategies/auth0'; +import { OAuth2AuthOptions } from '../auth-strategies/oauth2'; +import { AzureAuthOptions } from '../auth-strategies/azure-oidc'; + +export * from './strategy'; export const CUSTOMER_METADATA_KEY = 'useSocialAuth'; export const AUTH_PROVIDER_KEY = 'authProvider'; export const EMAIL_VERIFIED_KEY = 'emailVerified'; -export const TWENTY_FOUR_HOURS_IN_MS = 24 * 60 * 60 * 1000; - -export type StrategyExport = { - load: (container: MedusaContainer, configModule: ConfigModule, options?: unknown) => void; - getRouter?: (configModule: ConfigModule, options: AuthOptions) => Router[]; +export type StrategyExport = { + load: (container: MedusaContainer, configModule: ConfigModule, options?: AuthProvider & T) => void; + getRouter?: (configModule: ConfigModule, options: AuthProvider & T) => Router[]; }; /** * The options to set in the plugin configuration options property in the medusa-config.js file. */ -export type AuthOptions = ProviderOptions & { +export type AuthOptions = AuthProvider & ProviderOptions; + +export type AuthOptionsWrapper = + | AuthOptions + | ((configModule?: ConfigModule, container?: MedusaContainer) => PromiseLike) + | PromiseLike; + +export type ProviderOptions = + | GoogleAuthOptions + | FacebookAuthOptions + | LinkedinAuthOptions + | FirebaseAuthOptions + | Auth0Options + | AzureAuthOptions + | OAuth2AuthOptions; + +export type StrategyErrorIdentifierType = ProviderOptions['type']; + +export type AuthProvider = { + /** + * The type of provider to use. + * + * @default @see StrategyErrorIdentifierType + */ + identifier?: string; + /** * When set to admin | store | all, will only allow the user to authenticate using the provider * that has been used to create the account on the domain that strict is set to. @@ -44,51 +57,18 @@ export type AuthOptions = ProviderOptions & { strict?: 'admin' | 'store' | 'all' | 'none'; }; -export type ProviderOptions = { - google?: GoogleAuthOptions; - facebook?: FacebookAuthOptions; - linkedin?: LinkedinAuthOptions; - firebase?: FirebaseAuthOptions; - auth0?: Auth0Options; - azure_oidc?: AzureAuthOptions; - oauth2?: OAuth2AuthOptions; -}; +export function handleOption( + opt: AuthOptionsWrapper, + configModule?: ConfigModule, + container?: MedusaContainer, +): PromiseLike { + if (typeof opt === 'function') { + return handleOption(opt(configModule, container), configModule, container); + } -export type StrategyErrorIdentifierType = keyof ProviderOptions; -export type StrategyNames = { - [key in StrategyErrorIdentifierType]: { - admin: string; - store: string; - }; -}; + if (typeof opt === 'object' && typeof (opt as any).then === 'function') { + return opt as PromiseLike; + } -export const strategyNames: StrategyNames = { - auth0: { - admin: AUTH0_ADMIN_STRATEGY_NAME, - store: AUTH0_STORE_STRATEGY_NAME, - }, - facebook: { - admin: FACEBOOK_ADMIN_STRATEGY_NAME, - store: FACEBOOK_STORE_STRATEGY_NAME, - }, - google: { - admin: GOOGLE_ADMIN_STRATEGY_NAME, - store: GOOGLE_STORE_STRATEGY_NAME, - }, - linkedin: { - admin: LINKEDIN_ADMIN_STRATEGY_NAME, - store: LINKEDIN_STORE_STRATEGY_NAME, - }, - firebase: { - admin: FIREBASE_ADMIN_STRATEGY_NAME, - store: FIREBASE_STORE_STRATEGY_NAME, - }, - azure_oidc: { - admin: AZURE_ADMIN_STRATEGY_NAME, - store: AZURE_STORE_STRATEGY_NAME, - }, - oauth2: { - admin: OAUTH2_ADMIN_STRATEGY_NAME, - store: OAUTH2_STORE_STRATEGY_NAME, - }, -}; + return Promise.resolve(opt); +} diff --git a/packages/medusa-plugin-auth/src/types/strategy.ts b/packages/medusa-plugin-auth/src/types/strategy.ts new file mode 100644 index 00000000..9cbdacbd --- /dev/null +++ b/packages/medusa-plugin-auth/src/types/strategy.ts @@ -0,0 +1,13 @@ +import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; +import { AuthProvider } from './index'; + +export interface IStrategy { + validate(...args: any[]): any; +} + +export type StrategyFactory = new ( + container: MedusaContainer, + configModule: ConfigModule, + strategyOptions: T, + strict?: AuthProvider['strict'], +) => IStrategy; diff --git a/yarn.lock b/yarn.lock index 8e3b3c45..7b7fbaab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5133,7 +5133,47 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/passport-oauth2@^1.4.15": +"@types/passport-auth0@^1.0.9": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/passport-auth0/-/passport-auth0-1.0.9.tgz#989f3fc84d5300f16a8bc61b4a4c4e055a3ed2ca" + integrity sha512-xHYzOkq0qy0U/4QyUnB5JzutGrLARd435Q/5rr0e2kkW3Q49UTJkrohgOibFyw66bfV7aupkDu/in5WfMqJZSg== + dependencies: + "@types/express" "*" + "@types/passport" "*" + +"@types/passport-azure-ad@^4.3.5": + version "4.3.5" + resolved "https://registry.yarnpkg.com/@types/passport-azure-ad/-/passport-azure-ad-4.3.5.tgz#1c12281e581d439c3b6e6949d1f16243a6473773" + integrity sha512-dzWBDye7VvzWgKQbxImEvT0x1b0Vi37AYZrjN/XitTkstHsegDT97Wha5Aknoh4vPpv68DdaxZ4defK8YIk7kA== + dependencies: + "@types/express" "*" + "@types/passport" "*" + +"@types/passport-facebook@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/passport-facebook/-/passport-facebook-3.0.3.tgz#d73d79332b80372d5f38b2b6901e22c1326387c8" + integrity sha512-4cwyK2bGMo4Di8eMMLjf9JgDbpptRVYmStuy0ETZSaVo6fcY9+BtB9hCUmLEobUtqNHoIoXIWOCdDA2UynCUyg== + dependencies: + "@types/express" "*" + "@types/passport" "*" + "@types/passport-oauth2" "*" + +"@types/passport-google-oauth2@^0.1.8": + version "0.1.8" + resolved "https://registry.yarnpkg.com/@types/passport-google-oauth2/-/passport-google-oauth2-0.1.8.tgz#c2b7b86f83b74505bffb3fd87f3546e085e1923e" + integrity sha512-0lgGOVbGNTw7NcmrPh3pRwtGJXf1XZG7FZkYtM5DuQ8HJjHzi5mMIq28x7v/q5NNDIrFvhjE0CnyTv7hn4DIjg== + dependencies: + "@types/express" "*" + +"@types/passport-linkedin-oauth2@^1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@types/passport-linkedin-oauth2/-/passport-linkedin-oauth2-1.5.6.tgz#79549b526f0f5f0dbd8116156e759de1fdb3cda5" + integrity sha512-LlIwa+GGK8KoUHDxxwO2+5uqB6YmIHysqdLwpn+YJsjfmqFdAH+4YjhXO7riYwfYcpEr/pI+dSEDlwF0Xt+qhg== + dependencies: + "@types/express" "*" + "@types/passport" "*" + +"@types/passport-oauth2@*", "@types/passport-oauth2@^1.4.15": version "1.4.15" resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.15.tgz#34f2684f53aad36e664cd01ca9879224229f47e7" integrity sha512-9cUTP/HStNSZmhxXGuRrBJfEWzIEJRub2eyJu3CvkA+8HAMc9W3aKdFhVq+Qz1hi42qn+GvSAnz3zwacDSYWpw==