From 3e18cb3734ebc875b4459a18531b76b1b621adee Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Mon, 26 Jun 2023 14:27:28 +0200 Subject: [PATCH] feat: add support for strict options (#84) * feat: add support for strict options * chore: Make strict optional * feat: update strict values * more tests * update docs * update last tests * lint --- docs/pages/authentication/auth0.mdx | 3 +- docs/pages/authentication/azureoidc.mdx | 3 +- docs/pages/authentication/facebook.mdx | 3 +- docs/pages/authentication/firebase.mdx | 3 +- docs/pages/authentication/google.mdx | 3 +- docs/pages/authentication/linkedin.mdx | 3 +- .../__tests__/admin/verify-callback.spec.ts | 187 +++++++++----- .../__tests__/store/verify-callback.spec.ts | 236 ++++++++++++----- .../src/auth-strategies/auth0/admin.ts | 5 +- .../src/auth-strategies/auth0/index.ts | 4 +- .../src/auth-strategies/auth0/store.ts | 6 +- .../__tests__/admin/verify-callback.spec.ts | 190 ++++++++++---- .../__tests__/store/verify-callback.spec.ts | 241 +++++++++++++----- .../src/auth-strategies/azure-oidc/admin.ts | 8 +- .../src/auth-strategies/azure-oidc/index.ts | 4 +- .../src/auth-strategies/azure-oidc/store.ts | 8 +- .../__tests__/admin/verify-callback.spec.ts | 176 +++++++++---- .../__tests__/store/verify-callback.spec.ts | 225 +++++++++++----- .../src/auth-strategies/facebook/admin.ts | 11 +- .../src/auth-strategies/facebook/index.ts | 4 +- .../src/auth-strategies/facebook/store.ts | 11 +- .../src/auth-strategies/firebase/admin.ts | 10 +- .../src/auth-strategies/firebase/index.ts | 4 +- .../src/auth-strategies/firebase/store.ts | 10 +- .../__tests__/admin/verify-callback.spec.ts | 160 ++++++++---- .../__tests__/store/verify-callback.spec.ts | 213 +++++++++++----- .../src/auth-strategies/google/admin.ts | 10 +- .../src/auth-strategies/google/index.ts | 4 +- .../src/auth-strategies/google/store.ts | 11 +- .../__tests__/admin/verify-callback.spec.ts | 176 +++++++++---- .../__tests__/store/verify-callback.spec.ts | 225 +++++++++++----- .../src/auth-strategies/linkedin/admin.ts | 10 +- .../src/auth-strategies/linkedin/index.ts | 4 +- .../src/auth-strategies/linkedin/store.ts | 11 +- .../src/core/validate-callback.ts | 34 ++- .../medusa-plugin-auth/src/types/index.ts | 14 +- 36 files changed, 1613 insertions(+), 617 deletions(-) diff --git a/docs/pages/authentication/auth0.mdx b/docs/pages/authentication/auth0.mdx index 4e3d5a1..cdd7e66 100644 --- a/docs/pages/authentication/auth0.mdx +++ b/docs/pages/authentication/auth0.mdx @@ -35,6 +35,7 @@ newly added plugins. To do so here are the steps { resolve: "medusa-plugin-auth", options: { + // strict: "all", // or "none" or "store" or "admin" auth0: { clientID: Auth0ClientId, clientSecret: Auth0ClientSecret, @@ -84,7 +85,7 @@ newly added plugins. To do so here are the steps ### Default behaviour -The default `verifyCallback` flow looks as follow, +The default `verifyCallback` flow looks as follow (unless the `strict` option is changed to `none` or `store` or `admin` depending on the targeted domain) - for the `admin` - if the user trying to authenticate exists - then we are looking in the metadata to find if the strategy identifier is present in `authProvider`. diff --git a/docs/pages/authentication/azureoidc.mdx b/docs/pages/authentication/azureoidc.mdx index be5849f..3af770f 100644 --- a/docs/pages/authentication/azureoidc.mdx +++ b/docs/pages/authentication/azureoidc.mdx @@ -37,6 +37,7 @@ newly added plugins. To do so here are the steps resolve: "medusa-plugin-auth", options: { azure_oidc: { + // strict: "all", // or "none" or "store" or "admin" admin: { identityMetadata: AzureIdentityMetadata, clientID: AzureClientId, @@ -115,7 +116,7 @@ It has only been tested with default options. As of now only ResponseType.Code a ### Default behaviour -The default `verifyCallback` flow looks as follow, +The default `verifyCallback` flow looks as follow (unless the `strict` option is changed to `none` or `store` or `admin` depending on the targeted domain) - for the `admin` - if the user trying to authenticate exists - then we are looking in the metadata to find if the strategy identifier is present in `authProvider`. diff --git a/docs/pages/authentication/facebook.mdx b/docs/pages/authentication/facebook.mdx index 6848422..30b8b95 100644 --- a/docs/pages/authentication/facebook.mdx +++ b/docs/pages/authentication/facebook.mdx @@ -34,6 +34,7 @@ newly added plugins. To do so here are the steps { resolve: "medusa-plugin-auth", options: { + // strict: "all", // or "none" or "store" or "admin" facebook: { clientID: FacebookClientId, clientSecret: FacebookClientSecret, @@ -90,7 +91,7 @@ newly added plugins. To do so here are the steps ### Default behaviour -The default `verifyCallback` flow looks as follow, +The default `verifyCallback` flow looks as follow (unless the `strict` option is changed to `none` or `store` or `admin` depending on the targeted domain) - for the `admin` - if the user trying to authenticate exists - then we are looking in the metadata to find if the strategy identifier is present in `authProvider`. diff --git a/docs/pages/authentication/firebase.mdx b/docs/pages/authentication/firebase.mdx index b59bcec..737ac65 100644 --- a/docs/pages/authentication/firebase.mdx +++ b/docs/pages/authentication/firebase.mdx @@ -33,6 +33,7 @@ newly added plugins. To do so here are the steps { resolve: "medusa-plugin-auth", options: { + // strict: "all", // or "none" or "store" or "admin" firebase: { credentialJsonPath: CredentialJsonPath, @@ -78,7 +79,7 @@ newly added plugins. To do so here are the steps ### Default behaviour -The default `verifyCallback` flow looks as follow, +The default `verifyCallback` flow looks as follow (unless the `strict` option is changed to `none` or `store` or `admin` depending on the targeted domain) - for the `admin` - if the user trying to authenticate exists - then we are looking in the metadata to find if the strategy identifier is present in `authProvider`. diff --git a/docs/pages/authentication/google.mdx b/docs/pages/authentication/google.mdx index 42b993f..1499a4a 100644 --- a/docs/pages/authentication/google.mdx +++ b/docs/pages/authentication/google.mdx @@ -34,6 +34,7 @@ newly added plugins. To do so here are the steps { resolve: "medusa-plugin-auth", options: { + // strict: "all", // or "none" or "store" or "admin" google: { clientID: GoogleClientId, clientSecret: GoogleClientSecret, @@ -90,7 +91,7 @@ newly added plugins. To do so here are the steps ### Default behaviour -The default `verifyCallback` flow looks as follow, +The default `verifyCallback` flow looks as follow (unless the `strict` option is changed to `none` or `store` or `admin` depending on the targeted domain) - for the `admin` - if the user trying to authenticate exists - then we are looking in the metadata to find if the strategy identifier is present in `authProvider`. diff --git a/docs/pages/authentication/linkedin.mdx b/docs/pages/authentication/linkedin.mdx index 73c36d2..bf03375 100644 --- a/docs/pages/authentication/linkedin.mdx +++ b/docs/pages/authentication/linkedin.mdx @@ -33,6 +33,7 @@ newly added plugins. To do so here are the steps ```js { resolve: "medusa-plugin-auth", + // strict: "all", // or "none" or "store" or "admin" options: { linkedin: { clientID: LinkedinClientId, @@ -90,7 +91,7 @@ newly added plugins. To do so here are the steps ### Default behaviour -The default `verifyCallback` flow looks as follow, +The default `verifyCallback` flow looks as follow (unless the `strict` option is changed to `none` or `store` or `admin` depending on the targeted domain) - for the `admin` - if the user trying to authenticate exists - then we are looking in the metadata to find if the strategy identifier is present in `authProvider`. 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 c615d1d..cd389d8 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 @@ -60,66 +60,139 @@ describe('Auth0 admin strategy verify callback', function () { return container_[name]; }, } as MedusaContainer; - - auth0AdminStrategy = new Auth0AdminStrategy( - container, - {} as ConfigModule, - { - auth0Domain: 'fakeDomain', - clientID: 'fake', - clientSecret: 'fake', - admin: { callbackUrl: '/fakeCallbackUrl' }, - } as Auth0Options - ); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should succeed', async () => { - profile = { - emails: [{ value: existsEmailWithProviderKey }], - }; - - const data = await auth0AdminStrategy.validate(req, accessToken, refreshToken, extraParams, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test2', - }) - ); - }); - - it('should fail when a user exists without the auth provider metadata', async () => { - profile = { - emails: [{ value: existsEmail }], - }; - - const err = await auth0AdminStrategy - .validate(req, accessToken, refreshToken, extraParams, profile) - .catch((err) => err); - expect(err).toEqual(new Error(`Admin with email ${existsEmail} already exists`)); }); - it('should fail when a user exists with the wrong auth provider key', async () => { - profile = { - emails: [{ value: existsEmailWithWrongProviderKey }], - }; - - const err = await auth0AdminStrategy - .validate(req, accessToken, refreshToken, extraParams, profile) - .catch((err) => err); - expect(err).toEqual(new Error(`Admin with email ${existsEmailWithWrongProviderKey} already exists`)); + describe('when strict is set to admin', function () { + beforeEach(() => { + auth0AdminStrategy = new Auth0AdminStrategy( + container, + {} as ConfigModule, + { + auth0Domain: 'fakeDomain', + clientID: 'fake', + clientSecret: 'fake', + admin: { callbackUrl: '/fakeCallbackUrl' }, + } as Auth0Options, + 'admin' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithProviderKey }], + }; + + const data = await auth0AdminStrategy.validate(req, accessToken, refreshToken, extraParams, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + }); + + it('should fail when a user exists without the auth provider metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const err = await auth0AdminStrategy + .validate(req, accessToken, refreshToken, extraParams, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Admin with email ${existsEmail} already exists`)); + }); + + it('should fail when a user exists with the wrong auth provider key', async () => { + profile = { + emails: [{ value: existsEmailWithWrongProviderKey }], + }; + + const err = await auth0AdminStrategy + .validate(req, accessToken, refreshToken, extraParams, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Admin with email ${existsEmailWithWrongProviderKey} already exists`)); + }); + + it('should fail when the user does not exist', async () => { + profile = { + emails: [{ value: 'fake' }], + }; + + const err = await auth0AdminStrategy + .validate(req, accessToken, refreshToken, extraParams, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + }); }); - it('should fail when the user does not exist', async () => { - profile = { - emails: [{ value: 'fake' }], - }; - - const err = await auth0AdminStrategy - .validate(req, accessToken, refreshToken, extraParams, profile) - .catch((err) => err); - expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + describe('when strict is set for store only', function () { + beforeEach(() => { + auth0AdminStrategy = new Auth0AdminStrategy( + container, + {} as ConfigModule, + { + auth0Domain: 'fakeDomain', + clientID: 'fake', + clientSecret: 'fake', + admin: { callbackUrl: '/fakeCallbackUrl' }, + } as Auth0Options, + 'store' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithProviderKey }], + }; + + const data = await auth0AdminStrategy.validate(req, accessToken, refreshToken, extraParams, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + }); + + it('should succeed when a user exists without the auth provider metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const data = await auth0AdminStrategy.validate(req, accessToken, refreshToken, extraParams, profile); + expect(data).toEqual({ + accessToken: undefined, + id: 'test', + }); + }); + + it('should succeed when a user exists with the wrong auth provider key', async () => { + profile = { + emails: [{ value: existsEmailWithWrongProviderKey }], + }; + + const data = await auth0AdminStrategy.validate(req, accessToken, refreshToken, extraParams, profile); + expect(data).toEqual({ + accessToken: undefined, + id: 'test3', + }); + }); + + it('should fail when the user does not exist', async () => { + profile = { + emails: [{ value: 'fake' }], + }; + + const err = await auth0AdminStrategy + .validate(req, accessToken, refreshToken, extraParams, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + }); }); }); 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 f318f50..0fd852e 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 @@ -90,87 +90,185 @@ describe('Auth0 store strategy verify callback', function () { return container_[name]; }, } as MedusaContainer; - - auth0StoreStrategy = new Auth0StoreStrategy( - container, - {} as ConfigModule, - { - auth0Domain: 'fakeDomain', - clientID: 'fake', - clientSecret: 'fake', - store: { callbackUrl: '/fakeCallbackUrl' }, - } as Auth0Options - ); }); - afterEach(() => { - jest.clearAllMocks(); - }); + describe('when strict is set to store', function () { + beforeEach(() => { + auth0StoreStrategy = new Auth0StoreStrategy( + container, + {} as ConfigModule, + { + auth0Domain: 'fakeDomain', + clientID: 'fake', + clientSecret: 'fake', + store: { callbackUrl: '/fakeCallbackUrl' }, + } as Auth0Options, + 'store' + ); + }); - it('should succeed', async () => { - profile = { - emails: [{ value: existsEmailWithMetaAndProviderKey }], - }; + afterEach(() => { + jest.clearAllMocks(); + }); - const data = await auth0StoreStrategy.validate(req, accessToken, refreshToken, extraParams, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test3', - }) - ); - }); + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithMetaAndProviderKey }], + }; - it('should fail when the customer exists without the metadata', async () => { - profile = { - emails: [{ value: existsEmail }], - }; + const data = await auth0StoreStrategy.validate(req, accessToken, refreshToken, extraParams, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); - const err = await auth0StoreStrategy - .validate(req, accessToken, refreshToken, extraParams, profile) - .catch((err) => err); - expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`)); - }); + it('should fail when the customer exists without the metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; - it('should update customer metadata when the customer exsits with ONLY customer metadata key', async () => { - profile = { - emails: [{ value: existsEmailWithMeta }], - }; + const err = await auth0StoreStrategy + .validate(req, accessToken, refreshToken, extraParams, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`)); + }); - const data = await auth0StoreStrategy.validate(req, accessToken, refreshToken, extraParams, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test2', - }) - ); - expect(updateFn).toHaveBeenCalledTimes(1); - }); + it('should update customer metadata when the customer exsits with ONLY customer metadata key', async () => { + profile = { + emails: [{ value: existsEmailWithMeta }], + }; - it('should fail when the metadata exists but auth provider key is wrong', async () => { - profile = { - emails: [{ value: existsEmailWithMetaButWrongProviderKey }], - }; + const data = await auth0StoreStrategy.validate(req, accessToken, refreshToken, extraParams, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + expect(updateFn).toHaveBeenCalledTimes(1); + }); + + it('should fail when the metadata exists but auth provider key is wrong', async () => { + profile = { + emails: [{ value: existsEmailWithMetaButWrongProviderKey }], + }; + + const err = await auth0StoreStrategy + .validate(req, accessToken, refreshToken, extraParams, profile) + .catch((err) => err); + expect(err).toEqual( + new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`) + ); + }); - const err = await auth0StoreStrategy - .validate(req, accessToken, refreshToken, extraParams, profile) - .catch((err) => err); - expect(err).toEqual(new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`)); + it('should succeed and create a new customer if it has not been found', async () => { + profile = { + emails: [{ value: 'fake' }], + name: { + givenName: 'test', + familyName: 'test', + }, + }; + + const data = await auth0StoreStrategy.validate(req, accessToken, refreshToken, extraParams, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + expect(createFn).toHaveBeenCalledTimes(1); + }); }); - it('should succeed and create a new customer if it has not been found', async () => { - profile = { - emails: [{ value: 'fake' }], - name: { - givenName: 'test', - familyName: 'test', - }, - }; + describe('when strict is set to admin', function () { + beforeEach(() => { + auth0StoreStrategy = new Auth0StoreStrategy( + container, + {} as ConfigModule, + { + auth0Domain: 'fakeDomain', + clientID: 'fake', + clientSecret: 'fake', + store: { callbackUrl: '/fakeCallbackUrl' }, + } as Auth0Options, + 'admin' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithMetaAndProviderKey }], + }; + + const data = await auth0StoreStrategy.validate(req, accessToken, refreshToken, extraParams, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); - const data = await auth0StoreStrategy.validate(req, accessToken, refreshToken, extraParams, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test', - }) - ); - expect(createFn).toHaveBeenCalledTimes(1); + it('should succeed when the customer exists without the metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const data = await auth0StoreStrategy.validate(req, accessToken, refreshToken, extraParams, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + }); + + it('should update customer metadata when the customer exsits with ONLY customer metadata key', async () => { + profile = { + emails: [{ value: existsEmailWithMeta }], + }; + + const data = await auth0StoreStrategy.validate(req, accessToken, refreshToken, extraParams, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + expect(updateFn).toHaveBeenCalledTimes(1); + }); + + it('should succeed when the metadata exists but auth provider key is wrong', async () => { + profile = { + emails: [{ value: existsEmailWithMetaButWrongProviderKey }], + }; + + const data = await auth0StoreStrategy.validate(req, accessToken, refreshToken, extraParams, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test4', + }) + ); + }); + + it('should succeed and create a new customer if it has not been found', async () => { + profile = { + emails: [{ value: 'fake' }], + name: { + givenName: 'test', + familyName: 'test', + }, + }; + + const data = await auth0StoreStrategy.validate(req, accessToken, refreshToken, extraParams, profile); + 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 ef8167b..810a2ba 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/auth0/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/auth0/admin.ts @@ -5,12 +5,14 @@ import { AUTH0_ADMIN_STRATEGY_NAME, Auth0Options, Profile, ExtraParams } from '. 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'; 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 strategyOptions: Auth0Options, + protected readonly strict?: AuthOptions['strict'] ) { super({ domain: strategyOptions.auth0Domain, @@ -47,6 +49,7 @@ export class Auth0AdminStrategy extends PassportStrategy(Auth0Strategy, AUTH0_AD const validateRes = await validateAdminCallback(profile, { container: this.container, strategyErrorIdentifier: 'auth0', + strict: this.strict, }); return { ...validateRes, 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 c0a1758..e7d9bad 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/auth0/index.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/auth0/index.ts @@ -11,11 +11,11 @@ export * from './types'; export default { load: (container: MedusaContainer, configModule: ConfigModule, options: AuthOptions): void => { if (options.auth0?.admin) { - new Auth0AdminStrategy(container, configModule, options.auth0); + new Auth0AdminStrategy(container, configModule, options.auth0, options.strict); } if (options.auth0?.store) { - new Auth0StoreStrategy(container, configModule, options.auth0); + new Auth0StoreStrategy(container, configModule, options.auth0, options.strict); } }, getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { 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 cd3e450..1ea7983 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/auth0/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/auth0/store.ts @@ -5,12 +5,14 @@ import { Auth0Options, Profile, ExtraParams, AUTH0_STORE_STRATEGY_NAME } from '. 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'; 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 strategyOptions: Auth0Options, + protected readonly strict?: AuthOptions['strict'] ) { super({ domain: strategyOptions.auth0Domain, @@ -44,9 +46,11 @@ export class Auth0StoreStrategy extends PassportStrategy(Auth0Strategy, AUTH0_ST accessToken, }; } + const validateRes = await validateStoreCallback(profile, { container: this.container, strategyErrorIdentifier: 'auth0', + strict: this.strict, }); return { ...validateRes, 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 a23223e..7e4472e 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 @@ -55,65 +55,143 @@ describe('Azure AD admin strategy verify callback', function () { return container_[name]; }, } as MedusaContainer; - - azureAdminStrategy = new AzureAdminStrategy( - container, - {} as ConfigModule, - { - admin: { - identityMetadata: 'https://login.microsoftonline.com/common/.well-known/openid-configuration', - clientID: 'fake', - clientSecret: 'fake', - successRedirect: '/admin/auth/azure', - failureRedirect: 'http://localhost:9000/app/login', - callbackUrl: 'http://localhost:9000/admin/auth/azure/cb', - allowHttpForRedirectUrl: true, - }, - } as AzureAuthOptions - ); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should succeed', async () => { - profile = { - upn: existsEmailWithProviderKey, - }; - - const data = await azureAdminStrategy.validate(req, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test2', - }) - ); - }); - - it('should fail when a user exists without the auth provider metadata', async () => { - profile = { - upn: existsEmail, - }; - - const err = await azureAdminStrategy.validate(req, profile).catch((err) => err); - expect(err).toEqual(new Error(`Admin with email ${existsEmail} already exists`)); }); - it('should fail when a user exists with the wrong auth provider key', async () => { - profile = { - upn: existsEmailWithWrongProviderKey, - }; - - const err = await azureAdminStrategy.validate(req, profile).catch((err) => err); - expect(err).toEqual(new Error(`Admin with email ${existsEmailWithWrongProviderKey} already exists`)); + describe('when strict is set to admin', function () { + beforeEach(() => { + azureAdminStrategy = new AzureAdminStrategy( + container, + {} as ConfigModule, + { + admin: { + identityMetadata: 'https://login.microsoftonline.com/common/.well-known/openid-configuration', + clientID: 'fake', + clientSecret: 'fake', + successRedirect: '/admin/auth/azure', + failureRedirect: 'http://localhost:9000/app/login', + callbackUrl: 'http://localhost:9000/admin/auth/azure/cb', + allowHttpForRedirectUrl: true, + }, + } as AzureAuthOptions, + 'admin' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + upn: existsEmailWithProviderKey, + }; + + const data = await azureAdminStrategy.validate(req, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + }); + + it('should fail when a user exists without the auth provider metadata', async () => { + profile = { + upn: existsEmail, + }; + + const err = await azureAdminStrategy.validate(req, profile).catch((err) => err); + expect(err).toEqual(new Error(`Admin with email ${existsEmail} already exists`)); + }); + + it('should fail when a user exists with the wrong auth provider key', async () => { + profile = { + upn: existsEmailWithWrongProviderKey, + }; + + const err = await azureAdminStrategy.validate(req, profile).catch((err) => err); + expect(err).toEqual(new Error(`Admin with email ${existsEmailWithWrongProviderKey} already exists`)); + }); + + it('should fail when the user does not exist', async () => { + profile = { + upn: 'fake', + }; + + const err = await azureAdminStrategy.validate(req, profile).catch((err) => err); + expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + }); }); - it('should fail when the user does not exist', async () => { - profile = { - upn: 'fake', - }; - - const err = await azureAdminStrategy.validate(req, profile).catch((err) => err); - expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + describe('when strict is set to store', function () { + beforeEach(() => { + azureAdminStrategy = new AzureAdminStrategy( + container, + {} as ConfigModule, + { + admin: { + identityMetadata: 'https://login.microsoftonline.com/common/.well-known/openid-configuration', + clientID: 'fake', + clientSecret: 'fake', + successRedirect: '/admin/auth/azure', + failureRedirect: 'http://localhost:9000/app/login', + callbackUrl: 'http://localhost:9000/admin/auth/azure/cb', + allowHttpForRedirectUrl: true, + }, + } as AzureAuthOptions, + 'store' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + upn: existsEmailWithProviderKey, + }; + + const data = await azureAdminStrategy.validate(req, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + }); + + it('should succeed when a user exists without the auth provider metadata', async () => { + profile = { + upn: existsEmail, + }; + + const data = await azureAdminStrategy.validate(req, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + }); + + it('should succeed when a user exists with the wrong auth provider key', async () => { + profile = { + upn: existsEmailWithWrongProviderKey, + }; + + const data = await azureAdminStrategy.validate(req, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); + + it('should fail when the user does not exist', async () => { + profile = { + upn: 'fake', + }; + + const err = await azureAdminStrategy.validate(req, profile).catch((err) => err); + expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + }); }); }); 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 136b613..30d4e8a 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 @@ -86,88 +86,191 @@ describe('Google store strategy verify callback', function () { return container_[name]; }, } as MedusaContainer; + }); + + describe('when strict is set to store', function () { + beforeEach(() => { + azureStoreStrategy = new AzureStoreStrategy( + container, + {} as ConfigModule, + { + store: { + identityMetadata: 'https://login.microsoftonline.com/common/.well-known/openid-configuration', + clientID: 'fake', + clientSecret: 'fake', + successRedirect: '/admin/auth/azure', + failureRedirect: 'http://localhost:9000/app/login', + callbackUrl: 'http://localhost:9000/admin/auth/azure/cb', + allowHttpForRedirectUrl: true, + }, + } as AzureAuthOptions, + 'store' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + upn: existsEmailWithMetaAndProviderKey, + }; + + const data = await azureStoreStrategy.validate(req, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); + + it('should fail when the customer exists without the metadata', async () => { + profile = { + upn: existsEmail, + }; + + const err = await azureStoreStrategy.validate(req, profile).catch((err) => err); + expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`)); + }); - azureStoreStrategy = new AzureStoreStrategy( - container, - {} as ConfigModule, - { - store: { - identityMetadata: 'https://login.microsoftonline.com/common/.well-known/openid-configuration', - clientID: 'fake', - clientSecret: 'fake', - successRedirect: '/admin/auth/azure', - failureRedirect: 'http://localhost:9000/app/login', - callbackUrl: 'http://localhost:9000/admin/auth/azure/cb', - allowHttpForRedirectUrl: true, + it('should set AUTH_PROVIDER_KEY when CUSTOMER_METADATA_KEY exists but AUTH_PROVIDER_KEY does not', async () => { + profile = { + upn: existsEmailWithMeta, + }; + + const data = await azureStoreStrategy.validate(req, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + expect(updateFn).toHaveBeenCalledTimes(1); + }); + + it('should fail when the metadata exists but auth provider key is wrong', async () => { + profile = { + upn: existsEmailWithMetaButWrongProviderKey, + }; + + const err = await azureStoreStrategy.validate(req, profile).catch((err) => err); + expect(err).toEqual( + new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`) + ); + }); + + it('should succeed and create a new customer if it has not been found', async () => { + profile = { + upn: 'fake', + name: { + givenName: 'test', + familyName: 'test', }, - } as AzureAuthOptions - ); - }); + }; - afterEach(() => { - jest.clearAllMocks(); + const data = await azureStoreStrategy.validate(req, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + expect(createFn).toHaveBeenCalledTimes(1); + }); }); - it('should succeed', async () => { - profile = { - upn: existsEmailWithMetaAndProviderKey, - }; + describe('when strict is set to admin', function () { + beforeEach(() => { + azureStoreStrategy = new AzureStoreStrategy( + container, + {} as ConfigModule, + { + store: { + identityMetadata: 'https://login.microsoftonline.com/common/.well-known/openid-configuration', + clientID: 'fake', + clientSecret: 'fake', + successRedirect: '/admin/auth/azure', + failureRedirect: 'http://localhost:9000/app/login', + callbackUrl: 'http://localhost:9000/admin/auth/azure/cb', + allowHttpForRedirectUrl: true, + }, + } as AzureAuthOptions, + 'admin' + ); + }); - const data = await azureStoreStrategy.validate(req, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test3', - }) - ); - }); + afterEach(() => { + jest.clearAllMocks(); + }); - it('should fail when the customer exists without the metadata', async () => { - profile = { - upn: existsEmail, - }; + it('should succeed', async () => { + profile = { + upn: existsEmailWithMetaAndProviderKey, + }; - const err = await azureStoreStrategy.validate(req, profile).catch((err) => err); - expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`)); - }); + const data = await azureStoreStrategy.validate(req, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); - it('should set AUTH_PROVIDER_KEY when CUSTOMER_METADATA_KEY exists but AUTH_PROVIDER_KEY does not', async () => { - profile = { - upn: existsEmailWithMeta, - }; + it('should succeed when the customer exists without the metadata', async () => { + profile = { + upn: existsEmail, + }; - const data = await azureStoreStrategy.validate(req, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test2', - }) - ); - expect(updateFn).toHaveBeenCalledTimes(1); - }); + const data = await azureStoreStrategy.validate(req, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + }); - it('should fail when the metadata exists but auth provider key is wrong', async () => { - profile = { - upn: existsEmailWithMetaButWrongProviderKey, - }; + it('should set AUTH_PROVIDER_KEY when CUSTOMER_METADATA_KEY exists but AUTH_PROVIDER_KEY does not', async () => { + profile = { + upn: existsEmailWithMeta, + }; - const err = await azureStoreStrategy.validate(req, profile).catch((err) => err); - expect(err).toEqual(new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`)); - }); + const data = await azureStoreStrategy.validate(req, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + expect(updateFn).toHaveBeenCalledTimes(1); + }); - it('should succeed and create a new customer if it has not been found', async () => { - profile = { - upn: 'fake', - name: { - givenName: 'test', - familyName: 'test', - }, - }; + it('should succeed when the metadata exists but auth provider key is wrong', async () => { + profile = { + upn: existsEmailWithMetaButWrongProviderKey, + }; - const data = await azureStoreStrategy.validate(req, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test', - }) - ); - expect(createFn).toHaveBeenCalledTimes(1); + const data = await azureStoreStrategy.validate(req, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test4', + }) + ); + }); + + it('should succeed and create a new customer if it has not been found', async () => { + profile = { + upn: 'fake', + name: { + givenName: 'test', + familyName: 'test', + }, + }; + + const data = await azureStoreStrategy.validate(req, profile); + 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 fea4944..b0f79da 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 @@ -5,12 +5,14 @@ import { AZURE_ADMIN_STRATEGY_NAME, AzureAuthOptions, Profile, ResponseType, Res 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'; 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 strategyOptions: AzureAuthOptions, + protected readonly strict?: AuthOptions['strict'] ) { super({ identityMetadata: strategyOptions.admin.identityMetadata, @@ -27,7 +29,7 @@ export class AzureAdminStrategy extends PassportStrategy(AzureStrategy, AZURE_AD }); } - async validate(req: Request, profile: any, done?: Function): Promise { + async validate(req: Request, profile: any): Promise { if (this.strategyOptions.admin.verifyCallback) { return await this.strategyOptions.admin.verifyCallback(this.container, req, profile); } @@ -36,9 +38,11 @@ export class AzureAdminStrategy extends PassportStrategy(AzureStrategy, AZURE_AD 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, }); } } 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 526ca38..3c0e983 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 @@ -11,11 +11,11 @@ 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); + new AzureAdminStrategy(container, configModule, options.azure_oidc, options.strict); } if (options.azure_oidc?.store) { - new AzureStoreStrategy(container, configModule, options.azure_oidc); + new AzureStoreStrategy(container, configModule, options.azure_oidc, options.strict); } }, getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { 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 d4e46c1..87ae3f6 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 @@ -5,12 +5,14 @@ import { PassportStrategy } from '../../core/passport/Strategy'; import { AZURE_STORE_STRATEGY_NAME, AzureAuthOptions, Profile, ResponseType, ResponseMode } from './types'; import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; import { validateStoreCallback } from '../../core/validate-callback'; +import { AuthOptions } 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 strategyOptions: AzureAuthOptions, + protected readonly strict?: AuthOptions['strict'] ) { super({ identityMetadata: strategyOptions.store.identityMetadata, @@ -27,7 +29,7 @@ export class AzureStoreStrategy extends PassportStrategy(AzureStrategy, AZURE_ST }); } - async validate(req: Request, profile: any, done?: Function): Promise { + async validate(req: Request, profile: any): Promise { if (this.strategyOptions.store.verifyCallback) { return await this.strategyOptions.store.verifyCallback(this.container, req, profile); } @@ -36,9 +38,11 @@ export class AzureStoreStrategy extends PassportStrategy(AzureStrategy, AZURE_ST emails: [{ value: profile?.upn }], name: { givenName: profile?.name?.givenName, familyName: profile?.name?.familyName }, }; + return await validateStoreCallback(authprofile, { container: this.container, strategyErrorIdentifier: 'azure_oidc', + strict: this.strict, }); } } 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 e43969e..b302a0a 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 @@ -57,55 +57,139 @@ describe('Facebook admin strategy verify callback', function () { return container_[name]; }, } as MedusaContainer; - - facebookAdminStrategy = new FacebookAdminStrategy( - container, - {} as ConfigModule, - { clientID: 'fake', clientSecret: 'fake', admin: {} } as FacebookAuthOptions - ); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should succeed', async () => { - profile = { - emails: [{ value: existsEmailWithProviderKey }], - }; - - const data = await facebookAdminStrategy.validate(req, accessToken, refreshToken, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test2', - }) - ); - }); - - it('should fail when a user exists without the auth provider metadata', async () => { - profile = { - emails: [{ value: existsEmail }], - }; - - const err = await facebookAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Admin with email ${existsEmail} already exists`)); }); - it('should fail when a user exists with the wrong auth provider key', async () => { - profile = { - emails: [{ value: existsEmailWithWrongProviderKey }], - }; - - const err = await facebookAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Admin with email ${existsEmailWithWrongProviderKey} already exists`)); + describe('when strict is set to admin', function () { + beforeEach(() => { + facebookAdminStrategy = new FacebookAdminStrategy( + container, + {} as ConfigModule, + { + clientID: 'fake', + clientSecret: 'fake', + admin: {}, + } as FacebookAuthOptions, + 'admin' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithProviderKey }], + }; + + const data = await facebookAdminStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + }); + + it('should fail when a user exists without the auth provider metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const err = await facebookAdminStrategy + .validate(req, accessToken, refreshToken, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Admin with email ${existsEmail} already exists`)); + }); + + it('should fail when a user exists with the wrong auth provider key', async () => { + profile = { + emails: [{ value: existsEmailWithWrongProviderKey }], + }; + + const err = await facebookAdminStrategy + .validate(req, accessToken, refreshToken, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Admin with email ${existsEmailWithWrongProviderKey} already exists`)); + }); + + it('should fail when the user does not exist', async () => { + profile = { + emails: [{ value: 'fake' }], + }; + + const err = await facebookAdminStrategy + .validate(req, accessToken, refreshToken, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + }); }); - it('should fail when the user does not exist', async () => { - profile = { - emails: [{ value: 'fake' }], - }; - - const err = await facebookAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + describe('when strict is set to store', function () { + beforeEach(() => { + facebookAdminStrategy = new FacebookAdminStrategy( + container, + {} as ConfigModule, + { + clientID: 'fake', + clientSecret: 'fake', + admin: {}, + } as FacebookAuthOptions, + 'store' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithProviderKey }], + }; + + const data = await facebookAdminStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + }); + + it('should succeed when a user exists without the auth provider metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const data = await facebookAdminStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + }); + + it('should succeed when a user exists with the wrong auth provider key', async () => { + profile = { + emails: [{ value: existsEmailWithWrongProviderKey }], + }; + + const data = await facebookAdminStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); + + it('should fail when the user does not exist', async () => { + profile = { + emails: [{ value: 'fake' }], + }; + + const err = await facebookAdminStrategy + .validate(req, accessToken, refreshToken, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + }); }); }); 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 df0fe09..83fbfea 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 @@ -88,78 +88,183 @@ describe('Facebook store strategy verify callback', function () { return container_[name]; }, } as MedusaContainer; - - facebookStoreStrategy = new FacebookStoreStrategy( - container, - {} as ConfigModule, - { clientID: 'fake', clientSecret: 'fake', store: {} } as FacebookAuthOptions - ); }); - afterEach(() => { - jest.clearAllMocks(); - }); + describe('when strict is set to store', function () { + beforeEach(() => { + facebookStoreStrategy = new FacebookStoreStrategy( + container, + {} as ConfigModule, + { + clientID: 'fake', + clientSecret: 'fake', + store: {}, + } as FacebookAuthOptions, + 'store' + ); + }); - it('should succeed', async () => { - profile = { - emails: [{ value: existsEmailWithMetaAndProviderKey }], - }; + afterEach(() => { + jest.clearAllMocks(); + }); - const data = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test3', - }) - ); - }); + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithMetaAndProviderKey }], + }; - it('should fail when the customer exists without the metadata', async () => { - profile = { - emails: [{ value: existsEmail }], - }; + const data = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); - const err = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`)); - }); + it('should fail when the customer exists without the metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; - it('should set AUTH_PROVIDER_KEY when CUSTOMER_METADATA_KEY exists but AUTH_PROVIDER_KEY does not', async () => { - profile = { - emails: [{ value: existsEmailWithMeta }], - }; + const err = await facebookStoreStrategy + .validate(req, accessToken, refreshToken, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`)); + }); - const data = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test2', - }) - ); - expect(updateFn).toHaveBeenCalledTimes(1); - }); + it('should set AUTH_PROVIDER_KEY when CUSTOMER_METADATA_KEY exists but AUTH_PROVIDER_KEY does not', async () => { + profile = { + emails: [{ value: existsEmailWithMeta }], + }; - it('should fail when the metadata exists but auth provider key is wrong', async () => { - profile = { - emails: [{ value: existsEmailWithMetaButWrongProviderKey }], - }; + const data = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + expect(updateFn).toHaveBeenCalledTimes(1); + }); + + it('should fail when the metadata exists but auth provider key is wrong', async () => { + profile = { + emails: [{ value: existsEmailWithMetaButWrongProviderKey }], + }; + + const err = await facebookStoreStrategy + .validate(req, accessToken, refreshToken, profile) + .catch((err) => err); + expect(err).toEqual( + new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`) + ); + }); - const err = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`)); + it('should succeed and create a new customer if it has not been found', async () => { + profile = { + emails: [{ value: 'fake' }], + name: { + givenName: 'test', + familyName: 'test', + }, + }; + + const data = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + expect(createFn).toHaveBeenCalledTimes(1); + }); }); - it('should succeed and create a new customer if it has not been found', async () => { - profile = { - emails: [{ value: 'fake' }], - name: { - givenName: 'test', - familyName: 'test', - }, - }; + describe('when strict is set to admin', function () { + beforeEach(() => { + facebookStoreStrategy = new FacebookStoreStrategy( + container, + {} as ConfigModule, + { + clientID: 'fake', + clientSecret: 'fake', + store: {}, + } as FacebookAuthOptions, + 'admin' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithMetaAndProviderKey }], + }; + + const data = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); - const data = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test', - }) - ); - expect(createFn).toHaveBeenCalledTimes(1); + it('should fail when the customer exists without the metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const data = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + }); + + it('should set AUTH_PROVIDER_KEY when CUSTOMER_METADATA_KEY exists but AUTH_PROVIDER_KEY does not', async () => { + profile = { + emails: [{ value: existsEmailWithMeta }], + }; + + const data = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + expect(updateFn).toHaveBeenCalledTimes(1); + }); + + it('should succeed when the metadata exists but auth provider key is wrong', async () => { + profile = { + emails: [{ value: existsEmailWithMetaButWrongProviderKey }], + }; + + const data = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test4', + }) + ); + }); + + it('should succeed and create a new customer if it has not been found', async () => { + profile = { + emails: [{ value: 'fake' }], + name: { + givenName: 'test', + familyName: 'test', + }, + }; + + const data = await facebookStoreStrategy.validate(req, accessToken, refreshToken, profile); + 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 68b9210..1491a15 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/facebook/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/facebook/admin.ts @@ -5,12 +5,14 @@ import { FACEBOOK_ADMIN_STRATEGY_NAME, FacebookAuthOptions, Profile } from './ty 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'; 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 strategyOptions: FacebookAuthOptions, + protected readonly strict?: AuthOptions['strict'] ) { super({ clientID: strategyOptions.clientID, @@ -36,7 +38,12 @@ export class FacebookAdminStrategy extends PassportStrategy(FacebookStrategy, FA profile ); } - return await validateAdminCallback(profile, { container: this.container, strategyErrorIdentifier: 'facebook' }); + + return await validateAdminCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'facebook', + strict: this.strict, + }); } } 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 83092f0..45ade91 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/facebook/index.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/facebook/index.ts @@ -11,11 +11,11 @@ export * from './types'; export default { load: (container: MedusaContainer, configModule: ConfigModule, options: AuthOptions): void => { if (options.facebook?.admin) { - new FacebookAdminStrategy(container, configModule, options.facebook); + new FacebookAdminStrategy(container, configModule, options.facebook, options.strict); } if (options.facebook?.store) { - new FacebookStoreStrategy(container, configModule, options.facebook); + new FacebookStoreStrategy(container, configModule, options.facebook, options.strict); } }, getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { 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 c20134d..cd13bb3 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/facebook/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/facebook/store.ts @@ -5,12 +5,14 @@ import { FACEBOOK_STORE_STRATEGY_NAME, FacebookAuthOptions, Profile } from './ty 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'; 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 strategyOptions: FacebookAuthOptions, + protected readonly strict?: AuthOptions['strict'] ) { super({ clientID: strategyOptions.clientID, @@ -36,7 +38,12 @@ export class FacebookStoreStrategy extends PassportStrategy(FacebookStrategy, FA profile ); } - return await validateStoreCallback(profile, { container: this.container, strategyErrorIdentifier: 'facebook' }); + + return await validateStoreCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'facebook', + strict: this.strict, + }); } } 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 76b2c1c..e26974f 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/firebase/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/firebase/admin.ts @@ -6,12 +6,14 @@ 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'; 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 strategyOptions: FirebaseAuthOptions, + protected readonly strict?: AuthOptions['strict'] ) { super({ jwtFromRequest: strategyOptions.store.jwtFromRequest ?? ExtractJwt.fromAuthHeaderAsBearerToken(), @@ -26,7 +28,11 @@ export class FirebaseAdminStrategy extends PassportStrategy(FirebaseStrategy, FI } const profile: Profile = { emails: [{ value: decodedToken.email }] }; - return await validateAdminCallback(profile, { container: this.container, strategyErrorIdentifier: 'firebase' }); + return await validateAdminCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'firebase', + strict: this.strict, + }); } } 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 f5aace3..ce1c275 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/firebase/index.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/firebase/index.ts @@ -32,11 +32,11 @@ export default { } if (options.firebase?.admin) { - new FirebaseAdminStrategy(container, configModule, options.firebase); + new FirebaseAdminStrategy(container, configModule, options.firebase, options.strict); } if (options.firebase?.store) { - new FirebaseStoreStrategy(container, configModule, options.firebase); + new FirebaseStoreStrategy(container, configModule, options.firebase, options.strict); } }, getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { 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 d972757..3d445a7 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/firebase/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/firebase/store.ts @@ -6,12 +6,14 @@ 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'; 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 strategyOptions: FirebaseAuthOptions, + protected readonly strict?: AuthOptions['strict'] ) { super({ jwtFromRequest: strategyOptions.store.jwtFromRequest ?? ExtractJwt.fromAuthHeaderAsBearerToken(), @@ -26,7 +28,11 @@ export class FirebaseStoreStrategy extends PassportStrategy(FirebaseStrategy, FI } const profile: Profile = { emails: [{ value: decodedToken.email }] }; - return await validateStoreCallback(profile, { container: this.container, strategyErrorIdentifier: 'firebase' }); + return await validateStoreCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'firebase', + strict: this.strict, + }); } } 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 28bf839..bb41eff 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 @@ -57,55 +57,123 @@ describe('Google admin strategy verify callback', function () { return container_[name]; }, } as MedusaContainer; - - googleAdminStrategy = new GoogleAdminStrategy( - container, - {} as ConfigModule, - { clientID: 'fake', clientSecret: 'fake', admin: {} } as GoogleAuthOptions - ); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should succeed', async () => { - profile = { - emails: [{ value: existsEmailWithProviderKey }], - }; - - const data = await googleAdminStrategy.validate(req, accessToken, refreshToken, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test2', - }) - ); - }); - - it('should fail when a user exists without the auth provider metadata', async () => { - profile = { - emails: [{ value: existsEmail }], - }; - - const err = await googleAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Admin with email ${existsEmail} already exists`)); }); - it('should fail when a user exists with the wrong auth provider key', async () => { - profile = { - emails: [{ value: existsEmailWithWrongProviderKey }], - }; - - const err = await googleAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Admin with email ${existsEmailWithWrongProviderKey} already exists`)); + describe('when strict is set to admin', function () { + beforeEach(() => { + googleAdminStrategy = new GoogleAdminStrategy( + container, + {} as ConfigModule, + { clientID: 'fake', clientSecret: 'fake', admin: {} } as GoogleAuthOptions, + 'admin' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithProviderKey }], + }; + + const data = await googleAdminStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + }); + + it('should fail when a user exists without the auth provider metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const err = await googleAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); + expect(err).toEqual(new Error(`Admin with email ${existsEmail} already exists`)); + }); + + it('should fail when a user exists with the wrong auth provider key', async () => { + profile = { + emails: [{ value: existsEmailWithWrongProviderKey }], + }; + + const err = await googleAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); + expect(err).toEqual(new Error(`Admin with email ${existsEmailWithWrongProviderKey} already exists`)); + }); + + it('should fail when the user does not exist', async () => { + profile = { + emails: [{ value: 'fake' }], + }; + + const err = await googleAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); + expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + }); }); - it('should fail when the user does not exist', async () => { - profile = { - emails: [{ value: 'fake' }], - }; - - const err = await googleAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + describe('when strict is set for store only', function () { + beforeEach(() => { + googleAdminStrategy = new GoogleAdminStrategy( + container, + {} as ConfigModule, + { clientID: 'fake', clientSecret: 'fake', admin: {} } as GoogleAuthOptions, + 'store' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithProviderKey }], + }; + + const data = await googleAdminStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + }); + + it('should succeed when a user exists without the auth provider metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const data = await googleAdminStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + }); + + it('should succeed when a user exists with the wrong auth provider key', async () => { + profile = { + emails: [{ value: existsEmailWithWrongProviderKey }], + }; + + const data = await googleAdminStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); + + it('should fail when the user does not exist', async () => { + profile = { + emails: [{ value: 'fake' }], + }; + + const err = await googleAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); + expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + }); }); }); 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 9b5f2d5..0215ecc 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 @@ -88,78 +88,171 @@ describe('Google store strategy verify callback', function () { return container_[name]; }, } as MedusaContainer; - - googleStoreStrategy = new GoogleStoreStrategy( - container, - {} as ConfigModule, - { clientID: 'fake', clientSecret: 'fake', store: {} } as GoogleAuthOptions - ); }); - afterEach(() => { - jest.clearAllMocks(); - }); + describe('when strict is set to store', function () { + beforeEach(() => { + googleStoreStrategy = new GoogleStoreStrategy( + container, + {} as ConfigModule, + { clientID: 'fake', clientSecret: 'fake', store: {} } as GoogleAuthOptions, + 'store' + ); + }); - it('should succeed', async () => { - profile = { - emails: [{ value: existsEmailWithMetaAndProviderKey }], - }; + afterEach(() => { + jest.clearAllMocks(); + }); - const data = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test3', - }) - ); - }); + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithMetaAndProviderKey }], + }; - it('should fail when the customer exists without the metadata', async () => { - profile = { - emails: [{ value: existsEmail }], - }; + const data = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); - const err = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`)); - }); + it('should fail when the customer exists without the metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; - it('should set AUTH_PROVIDER_KEY when CUSTOMER_METADATA_KEY exists but AUTH_PROVIDER_KEY does not', async () => { - profile = { - emails: [{ value: existsEmailWithMeta }], - }; + const err = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); + expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`)); + }); - const data = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test2', - }) - ); - expect(updateFn).toHaveBeenCalledTimes(1); - }); + it('should set AUTH_PROVIDER_KEY when CUSTOMER_METADATA_KEY exists but AUTH_PROVIDER_KEY does not', async () => { + profile = { + emails: [{ value: existsEmailWithMeta }], + }; - it('should fail when the metadata exists but auth provider key is wrong', async () => { - profile = { - emails: [{ value: existsEmailWithMetaButWrongProviderKey }], - }; + const data = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + expect(updateFn).toHaveBeenCalledTimes(1); + }); + + it('should fail when the metadata exists but auth provider key is wrong', async () => { + profile = { + emails: [{ value: existsEmailWithMetaButWrongProviderKey }], + }; + + const err = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); + expect(err).toEqual( + new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`) + ); + }); - const err = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`)); + it('should succeed and create a new customer if it has not been found', async () => { + profile = { + emails: [{ value: 'fake' }], + name: { + givenName: 'test', + familyName: 'test', + }, + }; + + const data = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + expect(createFn).toHaveBeenCalledTimes(1); + }); }); - it('should succeed and create a new customer if it has not been found', async () => { - profile = { - emails: [{ value: 'fake' }], - name: { - givenName: 'test', - familyName: 'test', - }, - }; + describe('when strict is set to admin only', function () { + beforeEach(() => { + googleStoreStrategy = new GoogleStoreStrategy( + container, + {} as ConfigModule, + { clientID: 'fake', clientSecret: 'fake', store: {} } as GoogleAuthOptions, + 'admin' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithMetaAndProviderKey }], + }; + + const data = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); - const data = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test', - }) - ); - expect(createFn).toHaveBeenCalledTimes(1); + it('should succeed when the customer exists without the metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const data = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + }); + + it('should set AUTH_PROVIDER_KEY when CUSTOMER_METADATA_KEY exists but AUTH_PROVIDER_KEY does not', async () => { + profile = { + emails: [{ value: existsEmailWithMeta }], + }; + + const data = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + expect(updateFn).toHaveBeenCalledTimes(1); + }); + + it('should succeed when the metadata exists but auth provider key is wrong', async () => { + profile = { + emails: [{ value: existsEmailWithMetaButWrongProviderKey }], + }; + + const data = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test4', + }) + ); + }); + + it('should succeed and create a new customer if it has not been found', async () => { + profile = { + emails: [{ value: 'fake' }], + name: { + givenName: 'test', + familyName: 'test', + }, + }; + + const data = await googleStoreStrategy.validate(req, accessToken, refreshToken, profile); + 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 70695bb..d22062d 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/google/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/google/admin.ts @@ -5,12 +5,14 @@ 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'; 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 strategyOptions: GoogleAuthOptions, + protected readonly strict?: AuthOptions['strict'] ) { super({ clientID: strategyOptions.clientID, @@ -36,7 +38,11 @@ export class GoogleAdminStrategy extends PassportStrategy(GoogleStrategy, GOOGLE ); } - return await validateAdminCallback(profile, { container: this.container, strategyErrorIdentifier: 'google' }); + return await validateAdminCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'google', + strict: this.strict, + }); } } 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 5f4cf39..1f83ba0 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/google/index.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/google/index.ts @@ -11,11 +11,11 @@ export * from './store'; export default { load: (container: MedusaContainer, configModule: ConfigModule, options: AuthOptions): void => { if (options.google?.admin) { - new GoogleAdminStrategy(container, configModule, options.google); + new GoogleAdminStrategy(container, configModule, options.google, options.strict); } if (options.google?.store) { - new GoogleStoreStrategy(container, configModule, options.google); + new GoogleStoreStrategy(container, configModule, options.google, options.strict); } }, getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { 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 6854634..7519d3e 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/google/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/google/store.ts @@ -5,12 +5,14 @@ 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'; 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 strategyOptions: GoogleAuthOptions, + protected readonly strict?: AuthOptions['strict'] ) { super({ clientID: strategyOptions.clientID, @@ -35,7 +37,12 @@ export class GoogleStoreStrategy extends PassportStrategy(GoogleStrategy, GOOGLE profile ); } - return await validateStoreCallback(profile, { container: this.container, strategyErrorIdentifier: 'google' }); + + return await validateStoreCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'google', + strict: this.strict, + }); } } 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 1a3d4b2..d73b6c8 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 @@ -57,55 +57,139 @@ describe('Linkedin admin strategy verify callback', function () { return container_[name]; }, } as MedusaContainer; - - linkedinAdminStrategy = new LinkedinAdminStrategy( - container, - {} as ConfigModule, - { clientID: 'fake', clientSecret: 'fake', admin: {} } as LinkedinAuthOptions - ); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should succeed', async () => { - profile = { - emails: [{ value: existsEmailWithProviderKey }], - }; - - const data = await linkedinAdminStrategy.validate(req, accessToken, refreshToken, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test2', - }) - ); - }); - - it('should fail when a user exists without the auth provider metadata', async () => { - profile = { - emails: [{ value: existsEmail }], - }; - - const err = await linkedinAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Admin with email ${existsEmail} already exists`)); }); - it('should fail when a user exists with the wrong auth provider key', async () => { - profile = { - emails: [{ value: existsEmailWithWrongProviderKey }], - }; - - const err = await linkedinAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Admin with email ${existsEmailWithWrongProviderKey} already exists`)); + describe('when strict is set to admin', function () { + beforeEach(() => { + linkedinAdminStrategy = new LinkedinAdminStrategy( + container, + {} as ConfigModule, + { + clientID: 'fake', + clientSecret: 'fake', + admin: {}, + } as LinkedinAuthOptions, + 'admin' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithProviderKey }], + }; + + const data = await linkedinAdminStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + }); + + it('should fail when a user exists without the auth provider metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const err = await linkedinAdminStrategy + .validate(req, accessToken, refreshToken, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Admin with email ${existsEmail} already exists`)); + }); + + it('should fail when a user exists with the wrong auth provider key', async () => { + profile = { + emails: [{ value: existsEmailWithWrongProviderKey }], + }; + + const err = await linkedinAdminStrategy + .validate(req, accessToken, refreshToken, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Admin with email ${existsEmailWithWrongProviderKey} already exists`)); + }); + + it('should fail when the user does not exist', async () => { + profile = { + emails: [{ value: 'fake' }], + }; + + const err = await linkedinAdminStrategy + .validate(req, accessToken, refreshToken, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + }); }); - it('should fail when the user does not exist', async () => { - profile = { - emails: [{ value: 'fake' }], - }; - - const err = await linkedinAdminStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + describe('when strict is set to store', function () { + beforeEach(() => { + linkedinAdminStrategy = new LinkedinAdminStrategy( + container, + {} as ConfigModule, + { + clientID: 'fake', + clientSecret: 'fake', + admin: {}, + } as LinkedinAuthOptions, + 'store' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithProviderKey }], + }; + + const data = await linkedinAdminStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + }); + + it('should succeed when a user exists without the auth provider metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const data = await linkedinAdminStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + }); + + it('should succeed when a user exists with the wrong auth provider key', async () => { + profile = { + emails: [{ value: existsEmailWithWrongProviderKey }], + }; + + const data = await linkedinAdminStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); + + it('should fail when the user does not exist', async () => { + profile = { + emails: [{ value: 'fake' }], + }; + + const err = await linkedinAdminStrategy + .validate(req, accessToken, refreshToken, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + }); }); }); 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 62f4c20..64dce4b 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 @@ -88,78 +88,183 @@ describe('Linkedin store strategy verify callback', function () { return container_[name]; }, } as MedusaContainer; - - linkedinStoreStrategy = new LinkedinStoreStrategy( - container, - {} as ConfigModule, - { clientID: 'fake', clientSecret: 'fake', store: {} } as LinkedinAuthOptions - ); }); - afterEach(() => { - jest.clearAllMocks(); - }); + describe('when strict is set to store', function () { + beforeEach(() => { + linkedinStoreStrategy = new LinkedinStoreStrategy( + container, + {} as ConfigModule, + { + clientID: 'fake', + clientSecret: 'fake', + store: {}, + } as LinkedinAuthOptions, + 'store' + ); + }); - it('should succeed', async () => { - profile = { - emails: [{ value: existsEmailWithMetaAndProviderKey }], - }; + afterEach(() => { + jest.clearAllMocks(); + }); - const data = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test3', - }) - ); - }); + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithMetaAndProviderKey }], + }; - it('should fail when the customer exists without the metadata', async () => { - profile = { - emails: [{ value: existsEmail }], - }; + const data = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); - const err = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`)); - }); + it('should fail when the customer exists without the metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; - it('should set AUTH_PROVIDER_KEY when CUSTOMER_METADATA_KEY exists but AUTH_PROVIDER_KEY does not', async () => { - profile = { - emails: [{ value: existsEmailWithMeta }], - }; + const err = await linkedinStoreStrategy + .validate(req, accessToken, refreshToken, profile) + .catch((err) => err); + expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`)); + }); - const data = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test2', - }) - ); - expect(updateFn).toHaveBeenCalledTimes(1); - }); + it('should set AUTH_PROVIDER_KEY when CUSTOMER_METADATA_KEY exists but AUTH_PROVIDER_KEY does not', async () => { + profile = { + emails: [{ value: existsEmailWithMeta }], + }; - it('should fail when the metadata exists but auth provider key is wrong', async () => { - profile = { - emails: [{ value: existsEmailWithMetaButWrongProviderKey }], - }; + const data = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + expect(updateFn).toHaveBeenCalledTimes(1); + }); + + it('should fail when the metadata exists but auth provider key is wrong', async () => { + profile = { + emails: [{ value: existsEmailWithMetaButWrongProviderKey }], + }; + + const err = await linkedinStoreStrategy + .validate(req, accessToken, refreshToken, profile) + .catch((err) => err); + expect(err).toEqual( + new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`) + ); + }); - const err = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); - expect(err).toEqual(new Error(`Customer with email ${existsEmailWithMetaButWrongProviderKey} already exists`)); + it('should succeed and create a new customer if it has not been found', async () => { + profile = { + emails: [{ value: 'fake' }], + name: { + givenName: 'test', + familyName: 'test', + }, + }; + + const data = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + expect(createFn).toHaveBeenCalledTimes(1); + }); }); - it('should succeed and create a new customer if it has not been found', async () => { - profile = { - emails: [{ value: 'fake' }], - name: { - givenName: 'test', - familyName: 'test', - }, - }; + describe('when strict is set to admn', function () { + beforeEach(() => { + linkedinStoreStrategy = new LinkedinStoreStrategy( + container, + {} as ConfigModule, + { + clientID: 'fake', + clientSecret: 'fake', + store: {}, + } as LinkedinAuthOptions, + 'admin' + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithMetaAndProviderKey }], + }; + + const data = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }) + ); + }); - const data = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile); - expect(data).toEqual( - expect.objectContaining({ - id: 'test', - }) - ); - expect(createFn).toHaveBeenCalledTimes(1); + it('should succeed when the customer exists without the metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const data = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + }); + + it('should set AUTH_PROVIDER_KEY when CUSTOMER_METADATA_KEY exists but AUTH_PROVIDER_KEY does not', async () => { + profile = { + emails: [{ value: existsEmailWithMeta }], + }; + + const data = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + expect(updateFn).toHaveBeenCalledTimes(1); + }); + + it('should succeed when the metadata exists but auth provider key is wrong', async () => { + profile = { + emails: [{ value: existsEmailWithMetaButWrongProviderKey }], + }; + + const data = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test4', + }) + ); + }); + + it('should succeed and create a new customer if it has not been found', async () => { + profile = { + emails: [{ value: 'fake' }], + name: { + givenName: 'test', + familyName: 'test', + }, + }; + + const data = await linkedinStoreStrategy.validate(req, accessToken, refreshToken, profile); + 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 e17740b..df4013a 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/admin.ts @@ -5,12 +5,14 @@ import { LINKEDIN_ADMIN_STRATEGY_NAME, LinkedinAuthOptions, Profile } from './ty 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'; 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 strategyOptions: LinkedinAuthOptions, + protected readonly strict?: AuthOptions['strict'] ) { super({ clientID: strategyOptions.clientID, @@ -38,7 +40,11 @@ export class LinkedinAdminStrategy extends PassportStrategy(LinkedinStrategy, LI ); } - return await validateAdminCallback(profile, { container: this.container, strategyErrorIdentifier: 'linkedin' }); + return await validateAdminCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'linkedin', + strict: this.strict, + }); } } 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 a32df5d..3ef91c7 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/index.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/index.ts @@ -15,11 +15,11 @@ export * from './store'; export default { load: (container: MedusaContainer, configModule: ConfigModule, options: AuthOptions): void => { if (options.linkedin?.admin) { - new LinkedinAdminStrategy(container, configModule, options.linkedin); + new LinkedinAdminStrategy(container, configModule, options.linkedin, options.strict); } if (options.linkedin?.store) { - new LinkedinStoreStrategy(container, configModule, options.linkedin); + new LinkedinStoreStrategy(container, configModule, options.linkedin, options.strict); } }, getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { 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 fb8b5a4..bea529b 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/store.ts @@ -5,12 +5,14 @@ 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'; 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 strategyOptions: LinkedinAuthOptions, + protected readonly strict?: AuthOptions['strict'] ) { super({ clientID: strategyOptions.clientID, @@ -37,7 +39,12 @@ export class LinkedinStoreStrategy extends PassportStrategy(LinkedinStrategy, LI profile ); } - return await validateStoreCallback(profile, { container: this.container, strategyErrorIdentifier: 'linkedin' }); + + return await validateStoreCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'linkedin', + strict: this.strict, + }); } } diff --git a/packages/medusa-plugin-auth/src/core/validate-callback.ts b/packages/medusa-plugin-auth/src/core/validate-callback.ts index 1dc0f29..f2e0796 100644 --- a/packages/medusa-plugin-auth/src/core/validate-callback.ts +++ b/packages/medusa-plugin-auth/src/core/validate-callback.ts @@ -4,6 +4,7 @@ import { MedusaError } from 'medusa-core-utils'; import { EntityManager } from 'typeorm'; import { AUTH_PROVIDER_KEY, + AuthOptions, CUSTOMER_METADATA_KEY, EMAIL_VERIFIED_KEY, StrategyErrorIdentifierType, @@ -13,6 +14,10 @@ import { /** * Default validate callback used by an admin passport strategy * + * @param profile + * @param container + * @param strategyErrorIdentifier + * @param strict */ export async function validateAdminCallback< T extends { emails?: { value: string }[] } = { @@ -23,7 +28,12 @@ export async function validateAdminCallback< { container, strategyErrorIdentifier, - }: { container: MedusaContainer; strategyErrorIdentifier: StrategyErrorIdentifierType } + strict, + }: { + container: MedusaContainer; + strategyErrorIdentifier: StrategyErrorIdentifierType; + strict?: AuthOptions['strict']; + } ): Promise<{ id: string } | never> { const userService: UserService = container.resolve('userService'); const email = profile.emails?.[0]?.value; @@ -38,7 +48,11 @@ export async function validateAdminCallback< const user = await userService.retrieveByEmail(email).catch(() => void 0); if (user) { - if (!user.metadata || user.metadata[AUTH_PROVIDER_KEY] !== strategyNames[strategyErrorIdentifier].admin) { + strict ??= 'all'; + if ( + (strict === 'all' || strict === 'admin') && + (!user.metadata || user.metadata[AUTH_PROVIDER_KEY] !== strategyNames[strategyErrorIdentifier].admin) + ) { throw new MedusaError(MedusaError.Types.INVALID_DATA, `Admin with email ${email} already exists`); } } else { @@ -54,6 +68,7 @@ export async function validateAdminCallback< * @param profile * @param strategyErrorIdentifier It will be used to compose the error message in case of an error (e.g Google, Facebook) * @param container + * @param strict */ export async function validateStoreCallback< T extends { @@ -70,7 +85,12 @@ export async function validateStoreCallback< { container, strategyErrorIdentifier, - }: { container: MedusaContainer; strategyErrorIdentifier: StrategyErrorIdentifierType } + strict, + }: { + container: MedusaContainer; + strategyErrorIdentifier: StrategyErrorIdentifierType; + strict?: AuthOptions['strict']; + } ): Promise<{ id: string } | never> { const manager: EntityManager = container.resolve('manager'); const customerService: CustomerService = container.resolve('customerService'); @@ -116,10 +136,12 @@ export async function validateStoreCallback< }); } + strict ??= 'all'; if ( - !customer.metadata || - !customer.metadata[CUSTOMER_METADATA_KEY] || - customer.metadata[AUTH_PROVIDER_KEY] !== strategyNames[strategyErrorIdentifier].store + (strict === 'all' || strict === 'store') && + (!customer.metadata || + !customer.metadata[CUSTOMER_METADATA_KEY] || + customer.metadata[AUTH_PROVIDER_KEY] !== strategyNames[strategyErrorIdentifier].store) ) { throw new MedusaError(MedusaError.Types.INVALID_DATA, `Customer with email ${email} already exists`); } else { diff --git a/packages/medusa-plugin-auth/src/types/index.ts b/packages/medusa-plugin-auth/src/types/index.ts index 70e0121..e6bdb26 100644 --- a/packages/medusa-plugin-auth/src/types/index.ts +++ b/packages/medusa-plugin-auth/src/types/index.ts @@ -30,7 +30,17 @@ export type StrategyExport = { getRouter?: (configModule: ConfigModule, options: AuthOptions) => Router[]; }; -export type AuthOptions = { +export type AuthOptions = ProviderOptions & { + /** + * 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. + * + * @default 'all' + */ + strict?: 'admin' | 'store' | 'all' | 'none'; +}; + +export type ProviderOptions = { google?: GoogleAuthOptions; facebook?: FacebookAuthOptions; linkedin?: LinkedinAuthOptions; @@ -39,7 +49,7 @@ export type AuthOptions = { azure_oidc?: AzureAuthOptions; }; -export type StrategyErrorIdentifierType = keyof AuthOptions; +export type StrategyErrorIdentifierType = keyof ProviderOptions; export type StrategyNames = { [key in StrategyErrorIdentifierType]: { admin: string;