diff --git a/packages/medusa-plugin-auth/package.json b/packages/medusa-plugin-auth/package.json index 10339ce4..6f41c354 100644 --- a/packages/medusa-plugin-auth/package.json +++ b/packages/medusa-plugin-auth/package.json @@ -54,6 +54,7 @@ "@medusajs/medusa": ">=1.17.x", "@types/express": "^4.17.17", "@types/jest": "^29.1.2", + "@types/passport-oauth2": "^1.4.15", "jest": "^29.1.2", "passport": "^0.6.0", "ts-jest": "^29.0.3", @@ -72,7 +73,8 @@ "passport-facebook": "^3.0.0", "passport-firebase-jwt": "^1.2.1", "passport-google-oauth2": "^0.2.0", - "passport-linkedin-oauth2": "^2.0.0" + "passport-linkedin-oauth2": "^2.0.0", + "passport-oauth2": "^1.7.0" }, "jest": { "preset": "ts-jest", diff --git a/packages/medusa-plugin-auth/src/api/index.ts b/packages/medusa-plugin-auth/src/api/index.ts index 5ff7bee6..a4f11653 100644 --- a/packages/medusa-plugin-auth/src/api/index.ts +++ b/packages/medusa-plugin-auth/src/api/index.ts @@ -1,6 +1,7 @@ import { Router } from 'express'; import { ConfigModule } from '@medusajs/medusa/dist/types/global'; import loadConfig from '@medusajs/medusa/dist/loaders/config'; +import OAuth2Strategy from '../auth-strategies/oauth2'; import GoogleStrategy from '../auth-strategies/google'; import FacebookStrategy from '../auth-strategies/facebook'; import LinkedinStrategy from '../auth-strategies/linkedin'; @@ -18,6 +19,7 @@ export default function (rootDirectory, pluginOptions: AuthOptions): Router[] { function loadRouters(configModule: ConfigModule, options: AuthOptions): Router[] { const routers: Router[] = []; + routers.push(...OAuth2Strategy.getRouter(configModule, options)); routers.push(...GoogleStrategy.getRouter(configModule, options)); routers.push(...FacebookStrategy.getRouter(configModule, options)); routers.push(...LinkedinStrategy.getRouter(configModule, options)); diff --git a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/admin/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/admin/verify-callback.spec.ts new file mode 100644 index 00000000..42dc574f --- /dev/null +++ b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/admin/verify-callback.spec.ts @@ -0,0 +1,191 @@ +import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; +import { OAuth2AdminStrategy } from '../../admin'; +import { AUTH_PROVIDER_KEY } from '../../../../types'; +import { OAUTH2_ADMIN_STRATEGY_NAME, OAuth2AuthOptions } from '../../types'; + +describe('OAuth2 admin strategy verify callback', function() { + const existsEmail = 'exists@test.fr'; + const existsEmailWithProviderKey = 'exist3s@test.fr'; + const existsEmailWithWrongProviderKey = 'exist4s@test.fr'; + + let container: MedusaContainer; + let req: Request; + let accessToken: string; + let refreshToken: string; + let profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }; + let oauth2AdminStrategy: OAuth2AdminStrategy; + + beforeEach(() => { + profile = { + emails: [{ value: existsEmail }], + }; + + container = { + resolve: (name: string) => { + const container_ = { + userService: { + retrieveByEmail: jest.fn().mockImplementation(async (email: string) => { + if (email === existsEmail) { + return { + id: 'test', + }; + } + + if (email === existsEmailWithProviderKey) { + return { + id: 'test2', + metadata: { + [AUTH_PROVIDER_KEY]: OAUTH2_ADMIN_STRATEGY_NAME, + }, + }; + } + + if (email === existsEmailWithWrongProviderKey) { + return { + id: 'test3', + metadata: { + [AUTH_PROVIDER_KEY]: 'fake_provider_key', + }, + }; + } + + return; + }), + }, + }; + + return container_[name]; + }, + } as MedusaContainer; + }); + + describe('when strict is set to admin', function() { + beforeEach(() => { + oauth2AdminStrategy = new OAuth2AdminStrategy( + container, + {} as ConfigModule, + { + authorizationURL: 'http://localhost', + tokenURL: 'http://localhost', + clientID: 'fake', + clientSecret: 'fake', + admin: {}, + } as OAuth2AuthOptions, + 'admin', + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithProviderKey }], + }; + + const data = await oauth2AdminStrategy.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 oauth2AdminStrategy.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 oauth2AdminStrategy.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 oauth2AdminStrategy.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(() => { + oauth2AdminStrategy = new OAuth2AdminStrategy( + container, + {} as ConfigModule, + { + authorizationURL: 'http://localhost', + tokenURL: 'http://localhost', + clientID: 'fake', + clientSecret: 'fake', + admin: {}, + } as OAuth2AuthOptions, + 'store', + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithProviderKey }], + }; + + const data = await oauth2AdminStrategy.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 oauth2AdminStrategy.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 oauth2AdminStrategy.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 oauth2AdminStrategy.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/oauth2/__tests__/store/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/store/verify-callback.spec.ts new file mode 100644 index 00000000..13ff23c5 --- /dev/null +++ b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/__tests__/store/verify-callback.spec.ts @@ -0,0 +1,270 @@ +import { OAuth2StoreStrategy } from '../../store'; +import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; +import { AUTH_PROVIDER_KEY, CUSTOMER_METADATA_KEY } from '../../../../types'; +import { OAUTH2_STORE_STRATEGY_NAME, OAuth2AuthOptions, Profile } from '../../types'; + +describe('OAuth2 store strategy verify callback', function() { + const existsEmail = 'exists@test.fr'; + const existsEmailWithMeta = 'exist2s@test.fr'; + const existsEmailWithMetaAndProviderKey = 'exist3s@test.fr'; + const existsEmailWithMetaButWrongProviderKey = 'exist4s@test.fr'; + + let container: MedusaContainer; + let req: Request; + let accessToken: string; + let refreshToken: string; + let profile: Profile; + let oauth2StoreStrategy: OAuth2StoreStrategy; + let updateFn; + let createFn; + + beforeEach(() => { + profile = { + emails: [{ value: existsEmail }], + }; + + updateFn = jest.fn().mockImplementation(async () => { + return { id: 'test' }; + }); + createFn = jest.fn().mockImplementation(async () => { + return { id: 'test' }; + }); + + container = { + resolve: (name: string): T => { + const container_ = { + manager: { + transaction: function(cb) { + return cb(); + }, + }, + customerService: { + withTransaction: function() { + return this; + }, + update: updateFn, + create: createFn, + retrieveRegisteredByEmail: jest.fn().mockImplementation(async (email: string) => { + if (email === existsEmail) { + return { + id: 'test', + }; + } + + if (email === existsEmailWithMeta) { + return { + id: 'test2', + metadata: { + [CUSTOMER_METADATA_KEY]: true, + }, + }; + } + + if (email === existsEmailWithMetaAndProviderKey) { + return { + id: 'test3', + metadata: { + [CUSTOMER_METADATA_KEY]: true, + [AUTH_PROVIDER_KEY]: OAUTH2_STORE_STRATEGY_NAME, + }, + }; + } + + if (email === existsEmailWithMetaButWrongProviderKey) { + return { + id: 'test4', + metadata: { + [CUSTOMER_METADATA_KEY]: true, + [AUTH_PROVIDER_KEY]: 'fake_provider_key', + }, + }; + } + + return; + }), + }, + }; + + return container_[name]; + }, + } as MedusaContainer; + }); + + describe('when strict is set to store', function() { + beforeEach(() => { + oauth2StoreStrategy = new OAuth2StoreStrategy( + container, + {} as ConfigModule, + { + authorizationURL: 'http://localhost', + tokenURL: 'http://localhost', + clientID: 'fake', + clientSecret: 'fake', + store: {}, + } as OAuth2AuthOptions, + 'store', + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithMetaAndProviderKey }], + }; + + const data = await oauth2StoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }), + ); + }); + + it('should fail when the customer exists without the metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const err = await oauth2StoreStrategy.validate(req, accessToken, refreshToken, profile).catch((err) => err); + expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`)); + }); + + 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 oauth2StoreStrategy.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 oauth2StoreStrategy.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 oauth2StoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }), + ); + expect(createFn).toHaveBeenCalledTimes(1); + }); + }); + + describe('when strict is set to admin only', function() { + beforeEach(() => { + oauth2StoreStrategy = new OAuth2StoreStrategy( + container, + {} as ConfigModule, + { + authorizationURL: 'http://localhost', + tokenURL: 'http://localhost', + clientID: 'fake', + clientSecret: 'fake', + store: {}, + } as OAuth2AuthOptions, + 'admin', + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithMetaAndProviderKey }], + }; + + const data = await oauth2StoreStrategy.validate(req, accessToken, refreshToken, profile); + expect(data).toEqual( + expect.objectContaining({ + id: 'test3', + }), + ); + }); + + it('should succeed when the customer exists without the metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const data = await oauth2StoreStrategy.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 oauth2StoreStrategy.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 oauth2StoreStrategy.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 oauth2StoreStrategy.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/oauth2/admin.ts b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/admin.ts new file mode 100644 index 00000000..989b8d6a --- /dev/null +++ b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/admin.ts @@ -0,0 +1,73 @@ +import {Strategy as OAuth2Strategy, StrategyOptionsWithRequest} from 'passport-oauth2'; +import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; +import { Router } from 'express'; +import { OAUTH2_ADMIN_STRATEGY_NAME, OAuth2AuthOptions, Profile } from './types'; +import { PassportStrategy } from '../../core/passport/Strategy'; +import { validateAdminCallback } from '../../core/validate-callback'; +import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; +import { AuthOptions } from '../../types'; + +export class OAuth2AdminStrategy extends PassportStrategy(OAuth2Strategy, OAUTH2_ADMIN_STRATEGY_NAME) { + + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: OAuth2AuthOptions, + protected readonly strict?: AuthOptions['strict'] + ) { + super({ + authorizationURL: strategyOptions.authorizationURL, + tokenURL: strategyOptions.tokenURL, + clientID: strategyOptions.clientID, + clientSecret: strategyOptions.clientSecret, + callbackURL: strategyOptions.admin.callbackUrl, + passReqToCallback: true, + scope: strategyOptions.scope, + } as StrategyOptionsWithRequest); + } + + async validate( + req: Request, + accessToken: string, + refreshToken: string, + profile: Profile + ): Promise { + if (this.strategyOptions.admin.verifyCallback) { + return await this.strategyOptions.admin.verifyCallback( + this.container, + req, + accessToken, + refreshToken, + profile, + this.strict + ); + } + + return await validateAdminCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'oauth2', + strict: this.strict, + }); + } +} + +/** + * Return the router that hold the oauth2 admin authentication routes + * @param oauth2 + * @param configModule + */ +export function getOAuth2AdminAuthRouter(oauth2: OAuth2AuthOptions, configModule: ConfigModule): Router { + return passportAuthRoutesBuilder({ + domain: 'admin', + configModule, + authPath: oauth2.admin.authPath ?? '/admin/auth/oauth2', + authCallbackPath: oauth2.admin.authCallbackPath ?? '/admin/auth/oauth2/cb', + successRedirect: oauth2.admin.successRedirect, + strategyName: OAUTH2_ADMIN_STRATEGY_NAME, + passportAuthenticateMiddlewareOptions: {}, + passportCallbackAuthenticateMiddlewareOptions: { + failureRedirect: oauth2.admin.failureRedirect, + }, + expiresIn: oauth2.admin.expiresIn, + }); +} diff --git a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/index.ts b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/index.ts new file mode 100644 index 00000000..36077a45 --- /dev/null +++ b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/index.ts @@ -0,0 +1,34 @@ +import { AuthOptions, StrategyExport } from '../../types'; +import { Router } from 'express'; +import { getOAuth2AdminAuthRouter, OAuth2AdminStrategy } from './admin'; +import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; +import { getOAuth2StoreAuthRouter, OAuth2StoreStrategy } from './store'; + +export * from './types'; +export * from './admin'; +export * from './store'; + +export default { + load: (container: MedusaContainer, configModule: ConfigModule, options: AuthOptions): void => { + if (options.oauth2?.admin) { + new OAuth2AdminStrategy(container, configModule, options.oauth2, options.strict); + } + + if (options.oauth2?.store) { + new OAuth2StoreStrategy(container, configModule, options.oauth2, options.strict); + } + }, + getRouter: (configModule: ConfigModule, options: AuthOptions): Router[] => { + const routers = []; + + if (options.oauth2?.admin) { + routers.push(getOAuth2AdminAuthRouter(options.oauth2, configModule)); + } + + if (options.oauth2?.store) { + routers.push(getOAuth2StoreAuthRouter(options.oauth2, configModule)); + } + + return routers; + }, +} as StrategyExport; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/store.ts b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/store.ts new file mode 100644 index 00000000..4216a684 --- /dev/null +++ b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/store.ts @@ -0,0 +1,72 @@ +import { Router } from 'express'; +import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; +import {Strategy as OAuth2Strategy, StrategyOptionsWithRequest} from 'passport-oauth2'; +import { PassportStrategy } from '../../core/passport/Strategy'; +import { OAUTH2_STORE_STRATEGY_NAME, OAuth2AuthOptions, Profile } from './types'; +import { passportAuthRoutesBuilder } from '../../core/passport/utils/auth-routes-builder'; +import { validateStoreCallback } from '../../core/validate-callback'; +import { AuthOptions } from '../../types'; + +export class OAuth2StoreStrategy extends PassportStrategy(OAuth2Strategy, OAUTH2_STORE_STRATEGY_NAME) { + constructor( + protected readonly container: MedusaContainer, + protected readonly configModule: ConfigModule, + protected readonly strategyOptions: OAuth2AuthOptions, + protected readonly strict?: AuthOptions['strict'] + ) { + super({ + authorizationURL: strategyOptions.authorizationURL, + tokenURL: strategyOptions.tokenURL, + clientID: strategyOptions.clientID, + clientSecret: strategyOptions.clientSecret, + callbackURL: strategyOptions.store.callbackUrl, + passReqToCallback: true, + scope: strategyOptions.scope, + } as StrategyOptionsWithRequest); + } + + async validate( + req: Request, + accessToken: string, + refreshToken: string, + profile: Profile + ): Promise { + if (this.strategyOptions.store.verifyCallback) { + return await this.strategyOptions.store.verifyCallback( + this.container, + req, + accessToken, + refreshToken, + profile, + this.strict + ); + } + + return await validateStoreCallback(profile, { + container: this.container, + strategyErrorIdentifier: 'oauth2', + strict: this.strict, + }); + } +} + +/** + * Return the router that hold the oauth2 store authentication routes + * @param oauth2 + * @param configModule + */ +export function getOAuth2StoreAuthRouter(oauth2: OAuth2AuthOptions, configModule: ConfigModule): Router { + return passportAuthRoutesBuilder({ + domain: 'store', + configModule, + authPath: oauth2.store.authPath ?? '/store/auth/oauth2', + authCallbackPath: oauth2.store.authCallbackPath ?? '/store/auth/oauth2/cb', + successRedirect: oauth2.store.successRedirect, + strategyName: OAUTH2_STORE_STRATEGY_NAME, + passportAuthenticateMiddlewareOptions: {}, + passportCallbackAuthenticateMiddlewareOptions: { + failureRedirect: oauth2.store.failureRedirect, + }, + expiresIn: oauth2.store.expiresIn, + }); +} diff --git a/packages/medusa-plugin-auth/src/auth-strategies/oauth2/types.ts b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/types.ts new file mode 100644 index 00000000..ad5d50b8 --- /dev/null +++ b/packages/medusa-plugin-auth/src/auth-strategies/oauth2/types.ts @@ -0,0 +1,67 @@ +import { MedusaContainer } from '@medusajs/medusa/dist/types/global'; +import { AuthOptions } from '../../types'; + +export const OAUTH2_STORE_STRATEGY_NAME = 'oauth2.store.medusa-auth-plugin'; +export const OAUTH2_ADMIN_STRATEGY_NAME = 'oauth2.admin.medusa-auth-plugin'; + +export type Profile = { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }; + +export type OAuth2AuthOptions = { + authorizationURL: string; + tokenURL: string; + clientID: string; + clientSecret: string; + admin?: { + callbackUrl: string; + successRedirect: string; + failureRedirect: string; + /** + * Default /admin/auth/oauth2 + */ + authPath?: string; + /** + * Default /admin/auth/oauth2/cb + */ + authCallbackPath?: string; + /** + * The default verify callback function will be used if this configuration is not specified + */ + verifyCallback?: ( + container: MedusaContainer, + req: Request, + accessToken: string, + refreshToken: string, + profile: Profile, + strict?: AuthOptions['strict'] + ) => Promise; + + expiresIn?: number; + }; + store?: { + callbackUrl: string; + successRedirect: string; + failureRedirect: string; + /** + * Default /store/auth/oauth2 + */ + authPath?: string; + /** + * Default /store/auth/oauth2/cb + */ + authCallbackPath?: string; + /** + * The default verify callback function will be used if this configuration is not specified + */ + verifyCallback?: ( + container: MedusaContainer, + req: Request, + accessToken: string, + refreshToken: string, + profile: Profile, + strict?: AuthOptions['strict'] + ) => Promise; + + expiresIn?: number; + }; + scope?: string[]; +}; diff --git a/packages/medusa-plugin-auth/src/loaders/index.ts b/packages/medusa-plugin-auth/src/loaders/index.ts index 2c99627e..a8d3ebe9 100644 --- a/packages/medusa-plugin-auth/src/loaders/index.ts +++ b/packages/medusa-plugin-auth/src/loaders/index.ts @@ -1,6 +1,7 @@ import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; import { AuthOptions } from '../types'; +import OAuth2Strategy from '../auth-strategies/oauth2'; import GoogleStrategy from '../auth-strategies/google'; import FacebookStrategy from '../auth-strategies/facebook'; import LinkedinStrategy from '../auth-strategies/linkedin'; @@ -11,6 +12,7 @@ import AzureStrategy from '../auth-strategies/azure-oidc'; export default async function authStrategiesLoader(container: MedusaContainer, authOptions: AuthOptions) { const configModule = container.resolve('configModule') as ConfigModule; + OAuth2Strategy.load(container, configModule, authOptions); GoogleStrategy.load(container, configModule, authOptions); FacebookStrategy.load(container, configModule, authOptions); LinkedinStrategy.load(container, configModule, authOptions); diff --git a/packages/medusa-plugin-auth/src/types/index.ts b/packages/medusa-plugin-auth/src/types/index.ts index 340f0504..bfd73111 100644 --- a/packages/medusa-plugin-auth/src/types/index.ts +++ b/packages/medusa-plugin-auth/src/types/index.ts @@ -1,23 +1,24 @@ import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; import { Router } from 'express'; import { - FirebaseAuthOptions, FIREBASE_ADMIN_STRATEGY_NAME, FIREBASE_STORE_STRATEGY_NAME, + FirebaseAuthOptions, } from '../auth-strategies/firebase'; -import { GoogleAuthOptions, GOOGLE_ADMIN_STRATEGY_NAME, GOOGLE_STORE_STRATEGY_NAME } from '../auth-strategies/google'; +import { GOOGLE_ADMIN_STRATEGY_NAME, GOOGLE_STORE_STRATEGY_NAME, GoogleAuthOptions } from '../auth-strategies/google'; import { - FacebookAuthOptions, FACEBOOK_ADMIN_STRATEGY_NAME, FACEBOOK_STORE_STRATEGY_NAME, + FacebookAuthOptions, } from '../auth-strategies/facebook'; import { - LinkedinAuthOptions, LINKEDIN_ADMIN_STRATEGY_NAME, LINKEDIN_STORE_STRATEGY_NAME, + LinkedinAuthOptions, } from '../auth-strategies/linkedin'; -import { Auth0Options, AUTH0_ADMIN_STRATEGY_NAME, AUTH0_STORE_STRATEGY_NAME } from '../auth-strategies/auth0'; -import { AzureAuthOptions, AZURE_ADMIN_STRATEGY_NAME, AZURE_STORE_STRATEGY_NAME } from '../auth-strategies/azure-oidc'; +import { AUTH0_ADMIN_STRATEGY_NAME, AUTH0_STORE_STRATEGY_NAME, Auth0Options } from '../auth-strategies/auth0'; +import { AZURE_ADMIN_STRATEGY_NAME, AZURE_STORE_STRATEGY_NAME, AzureAuthOptions } from '../auth-strategies/azure-oidc'; +import { OAUTH2_ADMIN_STRATEGY_NAME, OAUTH2_STORE_STRATEGY_NAME, OAuth2AuthOptions } from '../auth-strategies/oauth2'; export const CUSTOMER_METADATA_KEY = 'useSocialAuth'; export const AUTH_PROVIDER_KEY = 'authProvider'; @@ -50,6 +51,7 @@ export type ProviderOptions = { firebase?: FirebaseAuthOptions; auth0?: Auth0Options; azure_oidc?: AzureAuthOptions; + oauth2?: OAuth2AuthOptions; }; export type StrategyErrorIdentifierType = keyof ProviderOptions; @@ -85,4 +87,8 @@ export const strategyNames: StrategyNames = { admin: AZURE_ADMIN_STRATEGY_NAME, store: AZURE_STORE_STRATEGY_NAME, }, + oauth2: { + admin: OAUTH2_ADMIN_STRATEGY_NAME, + store: OAUTH2_STORE_STRATEGY_NAME, + }, }; diff --git a/yarn.lock b/yarn.lock index 00718b2a..8e3b3c45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4882,6 +4882,16 @@ "@types/range-parser" "*" "@types/send" "*" +"@types/express@*": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/express@^4.17.14": version "4.17.14" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" @@ -5111,11 +5121,34 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== +"@types/oauth@*": + version "0.9.4" + resolved "https://registry.yarnpkg.com/@types/oauth/-/oauth-0.9.4.tgz#dcbab5efa2f34f312b915f80685760ccc8111e0a" + integrity sha512-qk9orhti499fq5XxKCCEbd0OzdPZuancneyse3KtR+vgMiHRbh+mn8M4G6t64ob/Fg+GZGpa565MF/2dKWY32A== + dependencies: + "@types/node" "*" + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/passport-oauth2@^1.4.15": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.15.tgz#34f2684f53aad36e664cd01ca9879224229f47e7" + integrity sha512-9cUTP/HStNSZmhxXGuRrBJfEWzIEJRub2eyJu3CvkA+8HAMc9W3aKdFhVq+Qz1hi42qn+GvSAnz3zwacDSYWpw== + dependencies: + "@types/express" "*" + "@types/oauth" "*" + "@types/passport" "*" + +"@types/passport@*": + version "1.0.16" + resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.16.tgz#5a2918b180a16924c4d75c31254c31cdca5ce6cf" + integrity sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A== + dependencies: + "@types/express" "*" + "@types/prettier@^2.1.5": version "2.7.1" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e" @@ -15093,6 +15126,17 @@ passport-oauth2@1.x.x, passport-oauth2@^1.1.2, passport-oauth2@^1.6.0, passport- uid2 "0.0.x" utils-merge "1.x.x" +passport-oauth2@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.7.0.tgz#5c4766c8531ac45ffe9ec2c09de9809e2c841fc4" + integrity sha512-j2gf34szdTF2Onw3+76alNnaAExlUmHvkc7cL+cmaS5NzHzDP/BvFHJruueQ9XAeNOdpI+CH+PWid8RA7KCwAQ== + dependencies: + base64url "3.x.x" + oauth "0.9.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + passport-oauth@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-oauth/-/passport-oauth-1.0.0.tgz#90aff63387540f02089af28cdad39ea7f80d77df"