diff --git a/packages/medusa-plugin-auth/README.md b/packages/medusa-plugin-auth/README.md index 158ecaa..7de57b3 100644 --- a/packages/medusa-plugin-auth/README.md +++ b/packages/medusa-plugin-auth/README.md @@ -75,27 +75,9 @@ Then, in your medusa config plugins collection you can add the following configu Now you can add your Google sign in button in your client with something along the line of the code bellow ```html - - - Sign in with Google + + + Sign in with Facebook ``` @@ -142,26 +124,8 @@ Then, in your medusa config plugins collection you can add the following configu Now you can add your Facebook sign in button in your client with something along the line of the code bellow ```html - - + + Sign in with Facebook ``` @@ -213,33 +177,60 @@ Then, in your medusa config plugins collection you can add the following configu Now you can add your Twitter sign in button in your client with something along the line of the code bellow ```html - - - Sign in with Twitter + + + Sign in with Twitter ``` ### Linkedin -Coming soon +> By default, the admin only allow to authenticate while the store create a new user of it does not exist yet. +> This behaviour can be changed and customised by specifying a custom `verifyCallback` in the configuration. + +Then, in your medusa config plugins collection you can add the following configuration and update it according to your requirements ([full configuration here](https://github.com/adrien2p/medusa-plugins/tree/main/packages/medusa-plugin-auth/src/auth-strategies/linkedin/types.ts)) + +```ts +{ + resolve: "medusa-plugin-auth", + options: { + // Enable linkedin OAuth 2 + linkedin: { + clientID: "__YOUR_CLIENT_ID__", + clientSecret: "__YOUR_CLIENT_SECRET__", + // Enable linkedin OAuth 2 for the admin domain + admin: { + callbackUrl:`${process.env.BACKEND_URL}/admin/auth/linkedin/cb`, + failureRedirect: `${process.env.ADMIN_URL}/login`, + successRedirect: `${process.env.ADMIN_URL}/`, + authPath: "/admin/auth/linkedin", + authCallbackPath: "/admin/auth/linkedin/cb", + + expiresIn: 24 * 60 * 60 * 1000 + }, + // Enable linkedin OAuth 2 for the store domain + store: { + callbackUrl:`${process.env.BACKEND_URL}/store/auth/linkedin/cb`, + failureRedirect: `${process.env.STORE_URL}/login`, + successRedirect: `${process.env.STORE_URL}/`, + authPath: "/store/auth/linkedin", + authCallbackPath: "/store/auth/linkedin/cb", + + expiresIn: 24 * 60 * 60 * 1000 + } + } + } +} +``` + +Now you can add your Linkedin sign in button in your client with something along the line of the code bellow + +```html + + + Sign in with Linkedin + +``` ### Github diff --git a/packages/medusa-plugin-auth/package.json b/packages/medusa-plugin-auth/package.json index f92faca..83400a7 100644 --- a/packages/medusa-plugin-auth/package.json +++ b/packages/medusa-plugin-auth/package.json @@ -63,7 +63,8 @@ "express": "^4.18.1", "jsonwebtoken": "^8.5.1", "passport-facebook": "^3.0.0", - "passport-google-oauth2": "^0.2.0" + "passport-google-oauth2": "^0.2.0", + "passport-linkedin-oauth2": "^2.0.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 0f4292f..39c5482 100644 --- a/packages/medusa-plugin-auth/src/api/index.ts +++ b/packages/medusa-plugin-auth/src/api/index.ts @@ -9,6 +9,7 @@ import { loadJwtOverrideStrategy } from '../auth-strategies/jwt-override'; import { getGoogleRoutes } from '../auth-strategies/google'; import { getFacebookRoutes } from '../auth-strategies/facebook'; import { getTwitterRoutes } from '../auth-strategies/twitter'; +import { getLinkedinRoutes } from "../auth-strategies/linkedin"; export default function (rootDirectory, pluginOptions: AuthOptions): Router[] { const configModule = loadConfig(rootDirectory) as ConfigModule; @@ -24,6 +25,7 @@ function loadRouters(configModule: ConfigModule, options: AuthOptions): Router[] routers.push(...getGoogleRoutes(configModule, options)); routers.push(...getFacebookRoutes(configModule, options)); routers.push(...getTwitterRoutes(configModule, options)); + routers.push(...getLinkedinRoutes(configModule, options)); routers.push(getLogoutRouter(configModule)); return routers; 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 new file mode 100644 index 0000000..3b36fa9 --- /dev/null +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/__tests__/admin/verify-callback.spec.ts @@ -0,0 +1,70 @@ +import { verifyAdminCallback } from '../../admin'; +import { MedusaContainer } from '@medusajs/medusa/dist/types/global'; + +describe('Linkedin admin strategy verify callback', function () { + const existsEmail = 'exists@test.fr'; + + let container: MedusaContainer; + let req: Request; + let accessToken: string; + let refreshToken: string; + let profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }; + + 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', + }; + } + + return; + }), + }, + }; + + return container_[name]; + }, + } as MedusaContainer; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should success', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const done = (err, data) => { + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + }; + + await verifyAdminCallback(container, req, accessToken, refreshToken, profile, done); + }); + + it('should fail if the user does not exists', async () => { + profile = { + emails: [{ value: 'fake' }], + }; + + const done = (err) => { + expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`)); + }; + + await verifyAdminCallback(container, req, accessToken, refreshToken, profile, done); + }); +}); 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 new file mode 100644 index 0000000..c019e34 --- /dev/null +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/__tests__/store/verify-callback.spec.ts @@ -0,0 +1,112 @@ +import { verifyStoreCallback } from '../../store'; +import { MedusaContainer } from '@medusajs/medusa/dist/types/global'; +import { ENTITY_METADATA_KEY } from '../../index'; + +describe('Linkedin store strategy verify callback', function () { + const existsEmail = 'exists@test.fr'; + const existsEmailWithMeta = 'exist2s@test.fr'; + + let container: MedusaContainer; + let req: Request; + let accessToken: string; + let refreshToken: string; + let profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }; + + beforeEach(() => { + profile = { + emails: [{ value: existsEmail }], + }; + + container = { + resolve: (name: string): T => { + const container_ = { + manager: { + transaction: function (cb) { + cb(); + }, + }, + customerService: { + withTransaction: function () { + return this; + }, + create: jest.fn().mockImplementation(async () => { + return { id: 'test' }; + }), + retrieveByEmail: jest.fn().mockImplementation(async (email: string) => { + if (email === existsEmail) { + return { + id: 'test', + }; + } + + if (email === existsEmailWithMeta) { + return { + id: 'test2', + metadata: { + [ENTITY_METADATA_KEY]: true, + }, + }; + } + + return; + }), + }, + }; + + return container_[name]; + }, + } as MedusaContainer; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should succeed', async () => { + profile = { + emails: [{ value: existsEmailWithMeta }], + }; + + const done = (err, data) => { + expect(data).toEqual( + expect.objectContaining({ + id: 'test2', + }) + ); + }; + + await verifyStoreCallback(container, req, accessToken, refreshToken, profile, done); + }); + + it('should fail when the customer exists without the metadata', async () => { + profile = { + emails: [{ value: existsEmail }], + }; + + const done = (err) => { + expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`)); + }; + + await verifyStoreCallback(container, req, accessToken, refreshToken, profile, done); + }); + + 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 done = (err, data) => { + expect(data).toEqual( + expect.objectContaining({ + id: 'test', + }) + ); + }; + + await verifyStoreCallback(container, req, accessToken, refreshToken, profile, done); + }); +}); diff --git a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/admin.ts b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/admin.ts new file mode 100644 index 0000000..3f27888 --- /dev/null +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/admin.ts @@ -0,0 +1,140 @@ +import passport from 'passport'; +import { Strategy as LinkedinStrategy } from 'passport-linkedin-oauth2'; +import jwt from 'jsonwebtoken'; +import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; +import { AUTH_TOKEN_COOKIE_NAME, TWENTY_FOUR_HOURS_IN_MS } from '../../types'; +import { UserService } from '@medusajs/medusa'; +import formatRegistrationName from '@medusajs/medusa/dist/utils/format-registration-name'; +import { MedusaError } from 'medusa-core-utils'; +import { Router } from 'express'; +import cors from 'cors'; +import { getCookieOptions } from '../../utils/get-cookie-options'; +import { LinkedinAuthOptions } from './types'; + +const LINKEDIN_ADMIN_STRATEGY_NAME = 'linkedin.admin.medusa-auth-plugin'; + +/** + * Load the linkedin strategy and attach the given verifyCallback or use the default implementation + * @param container + * @param configModule + * @param linkedin + */ +export function loadLinkedinAdminStrategy( + container: MedusaContainer, + configModule: ConfigModule, + linkedin: LinkedinAuthOptions +): void { + const verifyCallbackFn: LinkedinAuthOptions['admin']['verifyCallback'] = + linkedin.admin.verifyCallback ?? verifyAdminCallback; + + passport.use( + LINKEDIN_ADMIN_STRATEGY_NAME, + new LinkedinStrategy( + { + clientID: linkedin.clientID, + clientSecret: linkedin.clientSecret, + callbackURL: linkedin.admin.callbackUrl, + passReqToCallback: true, + scope: ['r_emailaddress'], + state: true, + }, + async ( + req: Request & { session: { jwt: string } }, + accessToken: string, + refreshToken: string, + profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }, + done: (err: null | unknown, data: null | { id: string }) => void + ) => { + const done_ = (err: null | unknown, data: null | { id: string }) => { + done(err, data); + }; + + await verifyCallbackFn(container, req, accessToken, refreshToken, profile, done_); + } + ) + ); +} + +/** + * Return the router that hold the linkedin admin authentication routes + * @param linkedin + * @param configModule + */ +export function getLinkedinAdminAuthRouter(linkedin: LinkedinAuthOptions, configModule: ConfigModule): Router { + const router = Router(); + + const adminCorsOptions = { + origin: configModule.projectConfig.admin_cors.split(','), + credentials: true, + }; + + router.get(linkedin.admin.authPath, cors(adminCorsOptions)); + router.get( + linkedin.admin.authPath, + passport.authenticate(LINKEDIN_ADMIN_STRATEGY_NAME, { + scope: [ + 'https://www.linkedinapis.com/auth/userinfo.email', + 'https://www.linkedinapis.com/auth/userinfo.profile', + ], + session: false, + }) + ); + + const callbackHandler = (req, res) => { + const token = jwt.sign({ userId: req.user.id }, configModule.projectConfig.jwt_secret, { + expiresIn: linkedin.admin.expiresIn ?? TWENTY_FOUR_HOURS_IN_MS, + }); + res.cookie(AUTH_TOKEN_COOKIE_NAME, token, getCookieOptions()).redirect(linkedin.admin.successRedirect); + }; + + router.get(linkedin.admin.authCallbackPath, cors(adminCorsOptions)); + router.get( + linkedin.admin.authCallbackPath, + (req, res, next) => { + if (req.user) { + callbackHandler(req, res); + } + + next(); + }, + passport.authenticate(LINKEDIN_ADMIN_STRATEGY_NAME, { + failureRedirect: linkedin.admin.failureRedirect, + session: false, + }), + callbackHandler + ); + + return router; +} + +/** + * Default callback to execute when the strategy is called. + * @param container + * @param req + * @param accessToken + * @param refreshToken + * @param profile + * @param done + */ +export async function verifyAdminCallback( + container: MedusaContainer, + req: Request, + accessToken: string, + refreshToken: string, + profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }, + done: (err: null | unknown, data: null | { id: string }) => void +): Promise { + const userService: UserService = container.resolve(formatRegistrationName(`${process.cwd()}/services/user.js`)); + const email = profile.emails[0].value; + + const user = await userService.retrieveByEmail(email).catch(() => void 0); + if (!user) { + const err = new MedusaError( + MedusaError.Types.NOT_ALLOWED, + `Unable to authenticate the user with the email ${email}` + ); + return done(err, null); + } + + return done(null, { id: user.id }); +} diff --git a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/index.ts b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/index.ts new file mode 100644 index 0000000..d1669fd --- /dev/null +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/index.ts @@ -0,0 +1,39 @@ +import { AuthOptions } from '../../types'; +import { Router } from 'express'; +import { getLinkedinAdminAuthRouter, loadLinkedinAdminStrategy } from './admin'; +import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; +import { getLinkedinStoreAuthRouter, loadLinkedinStoreStrategy } from './store'; + +export const ENTITY_METADATA_KEY = 'useLinkedinStrategy'; + +export * from './types'; +export * from './admin'; +export * from './store'; + +export function getLinkedinRoutes(configModule: ConfigModule, options: AuthOptions): Router[] { + const routers = []; + + if (options.linkedin?.admin) { + routers.push(getLinkedinAdminAuthRouter(options.linkedin, configModule)); + } + + if (options.linkedin?.store) { + routers.push(getLinkedinStoreAuthRouter(options.linkedin, configModule)); + } + + return routers; +} + +export function loadLinkedinStrategies( + container: MedusaContainer, + configModule: ConfigModule, + options: AuthOptions +): void { + if (options.linkedin?.admin) { + loadLinkedinAdminStrategy(container, configModule, options.linkedin); + } + + if (options.linkedin?.store) { + loadLinkedinStoreStrategy(container, configModule, options.linkedin); + } +} diff --git a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/store.ts b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/store.ts new file mode 100644 index 0000000..4af810c --- /dev/null +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/store.ts @@ -0,0 +1,162 @@ +import passport from 'passport'; +import { Router } from 'express'; +import cors from 'cors'; +import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; +import jwt from 'jsonwebtoken'; +import { Strategy as LinkedinStrategy } from 'passport-linkedin-oauth2'; +import { CustomerService } from '@medusajs/medusa'; +import formatRegistrationName from '@medusajs/medusa/dist/utils/format-registration-name'; +import { MedusaError } from 'medusa-core-utils'; +import { EntityManager } from 'typeorm'; + +import { AUTH_TOKEN_COOKIE_NAME, CUSTOMER_METADATA_KEY, TWENTY_FOUR_HOURS_IN_MS } from '../../types'; +import { getCookieOptions } from '../../utils/get-cookie-options'; +import { LinkedinAuthOptions } from './index'; + +const LINKEDIN_STORE_STRATEGY_NAME = 'linkedin.store.medusa-auth-plugin'; + +/** + * Load the linkedin strategy and attach the given verifyCallback or use the default implementation + * @param container + * @param configModule + * @param linkedin + */ +export function loadLinkedinStoreStrategy( + container: MedusaContainer, + configModule: ConfigModule, + linkedin: LinkedinAuthOptions +): void { + const verifyCallbackFn: LinkedinAuthOptions['store']['verifyCallback'] = + linkedin.admin.verifyCallback ?? verifyStoreCallback; + + passport.use( + LINKEDIN_STORE_STRATEGY_NAME, + new LinkedinStrategy( + { + clientID: linkedin.clientID, + clientSecret: linkedin.clientSecret, + callbackURL: linkedin.store.callbackUrl, + passReqToCallback: true, + scope: ['r_emailaddress'], + state: true, + }, + async function ( + req: Request & { session: { jwt: string } }, + accessToken: string, + refreshToken: string, + profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }, + done: (err: null | unknown, data: null | { id: string }) => void + ) { + const done_ = (err: null | unknown, data: null | { id: string }) => { + done(err, data); + }; + + await verifyCallbackFn(container, req, accessToken, refreshToken, profile, done_); + } + ) + ); +} + +/** + * Return the router that hold the linkedin store authentication routes + * @param linkedin + * @param configModule + */ +export function getLinkedinStoreAuthRouter(linkedin: LinkedinAuthOptions, configModule: ConfigModule): Router { + const router = Router(); + + const storeCorsOptions = { + origin: configModule.projectConfig.store_cors.split(','), + credentials: true, + }; + + router.get(linkedin.store.authPath, cors(storeCorsOptions)); + router.get( + linkedin.store.authPath, + passport.authenticate(LINKEDIN_STORE_STRATEGY_NAME, { + scope: [ + 'https://www.linkedinapis.com/auth/userinfo.email', + 'https://www.linkedinapis.com/auth/userinfo.profile', + ], + session: false, + }) + ); + + router.get(linkedin.store.authCallbackPath, cors(storeCorsOptions)); + router.get( + linkedin.store.authCallbackPath, + passport.authenticate(LINKEDIN_STORE_STRATEGY_NAME, { + failureRedirect: linkedin.store.failureRedirect, + session: false, + }), + (req, res) => { + const token = jwt.sign({ userId: req.user.id }, configModule.projectConfig.jwt_secret, { + expiresIn: linkedin.store.expiresIn ?? TWENTY_FOUR_HOURS_IN_MS, + }); + res.cookie(AUTH_TOKEN_COOKIE_NAME, token, getCookieOptions()).redirect(linkedin.admin.successRedirect); + } + ); + + return router; +} + +/** + * Default callback to execute when the strategy is called. + * @param container + * @param req + * @param accessToken + * @param refreshToken + * @param profile + * @param done + */ +export async function verifyStoreCallback( + container: MedusaContainer, + req: Request, + accessToken: string, + refreshToken: string, + profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }, + done: (err: null | unknown, data: null | { id: string }) => void +): Promise { + const manager: EntityManager = container.resolve('manager'); + const customerService: CustomerService = container.resolve( + formatRegistrationName(`${process.cwd()}/services/customer.js`) + ); + + await manager.transaction(async (transactionManager) => { + const email = profile.emails[0].value; + + const customer = await customerService + .withTransaction(transactionManager) + .retrieveByEmail(email) + .catch(() => void 0); + + if (customer) { + if (!customer.metadata || !customer.metadata[CUSTOMER_METADATA_KEY]) { + const err = new MedusaError( + MedusaError.Types.INVALID_DATA, + `Customer with email ${email} already exists` + ); + return done(err, null); + } else { + return done(null, { id: customer.id }); + } + } + + await customerService + .withTransaction(transactionManager) + .create({ + email, + metadata: { + [CUSTOMER_METADATA_KEY]: true, + }, + first_name: profile?.name.givenName ?? '', + last_name: profile?.name.familyName ?? '', + }) + .then((customer) => { + return done(null, { id: customer.id }); + }) + .catch((err) => { + return done(err, null); + }); + }); +} diff --git a/packages/medusa-plugin-auth/src/auth-strategies/linkedin/types.ts b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/types.ts new file mode 100644 index 0000000..087dcd2 --- /dev/null +++ b/packages/medusa-plugin-auth/src/auth-strategies/linkedin/types.ts @@ -0,0 +1,46 @@ +import { MedusaContainer } from '@medusajs/medusa/dist/types/global'; + +export type LinkedinAuthOptions = { + clientID: string; + clientSecret: string; + admin?: { + callbackUrl: string; + successRedirect: string; + failureRedirect: string; + authPath: string; + 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: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }, + done: (err: null | unknown, data: null | { id: string }) => void + ) => Promise; + + expiresIn?: string; + }; + store?: { + callbackUrl: string; + successRedirect: string; + failureRedirect: string; + authPath: string; + 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: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } }, + done: (err: null | unknown, data: null | { id: string }) => void + ) => Promise; + + expiresIn?: string; + }; +}; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/twitter/__tests__/store/verify-callback.spec.ts b/packages/medusa-plugin-auth/src/auth-strategies/twitter/__tests__/store/verify-callback.spec.ts index 0aa08d4..b5a7178 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/twitter/__tests__/store/verify-callback.spec.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/twitter/__tests__/store/verify-callback.spec.ts @@ -1,6 +1,6 @@ import { verifyStoreCallback } from '../../store'; import { MedusaContainer } from '@medusajs/medusa/dist/types/global'; -import { CUSTOMER_METADATA_KEY } from "../../../../types"; +import { CUSTOMER_METADATA_KEY } from '../../../../types'; describe('Twitter store strategy verify callback', function () { const existsEmail = 'exists@test.fr'; diff --git a/packages/medusa-plugin-auth/src/auth-strategies/twitter/admin.ts b/packages/medusa-plugin-auth/src/auth-strategies/twitter/admin.ts index 1950ac2..1b483a9 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/twitter/admin.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/twitter/admin.ts @@ -1,5 +1,5 @@ import passport from 'passport'; -import { Strategy as TwitterStrategy } from 'passport-twitter'; +import { Strategy as TwitterStrategy } from '@superfaceai/passport-twitter-oauth2'; import jwt from 'jsonwebtoken'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; import { AUTH_TOKEN_COOKIE_NAME, TWENTY_FOUR_HOURS_IN_MS } from '../../types'; @@ -37,6 +37,7 @@ export function loadTwitterAdminStrategy( passReqToCallback: true, clientType: 'private', scope: ['tweet.read', 'offline.access'], + state: true, }, async ( req: Request & { session: { jwt: string } }, diff --git a/packages/medusa-plugin-auth/src/auth-strategies/twitter/store.ts b/packages/medusa-plugin-auth/src/auth-strategies/twitter/store.ts index 24605ab..450045f 100644 --- a/packages/medusa-plugin-auth/src/auth-strategies/twitter/store.ts +++ b/packages/medusa-plugin-auth/src/auth-strategies/twitter/store.ts @@ -3,7 +3,7 @@ import { Router } from 'express'; import cors from 'cors'; import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global'; import jwt from 'jsonwebtoken'; -import { Strategy as TwitterStrategy } from 'passport-twitter'; +import { Strategy as TwitterStrategy } from '@superfaceai/passport-twitter-oauth2'; import { CustomerService } from '@medusajs/medusa'; import formatRegistrationName from '@medusajs/medusa/dist/utils/format-registration-name'; import { MedusaError } from 'medusa-core-utils'; @@ -39,6 +39,7 @@ export function loadTwitterStoreStrategy( passReqToCallback: true, clientType: 'private', scope: ['tweet.read', 'offline.access'], + state: true, }, async function ( req: Request & { session: { jwt: string } }, diff --git a/packages/medusa-plugin-auth/src/loaders/index.ts b/packages/medusa-plugin-auth/src/loaders/index.ts index eba921f..ebfcb56 100644 --- a/packages/medusa-plugin-auth/src/loaders/index.ts +++ b/packages/medusa-plugin-auth/src/loaders/index.ts @@ -4,6 +4,7 @@ import { AuthOptions } from '../types'; import { loadGoogleStrategies } from '../auth-strategies/google'; import { loadFacebookStrategies } from '../auth-strategies/facebook'; import { loadTwitterStrategies } from '../auth-strategies/twitter'; +import { loadLinkedinStrategies } from "../auth-strategies/linkedin"; export default async function authStrategiesLoader(container: MedusaContainer, authOptions: AuthOptions) { const configModule = container.resolve('configModule') as ConfigModule; @@ -11,4 +12,5 @@ export default async function authStrategiesLoader(container: MedusaContainer, a loadGoogleStrategies(container, configModule, authOptions); loadFacebookStrategies(container, configModule, authOptions); loadTwitterStrategies(container, configModule, authOptions); + loadLinkedinStrategies(container, configModule, authOptions); } diff --git a/packages/medusa-plugin-auth/src/types/index.ts b/packages/medusa-plugin-auth/src/types/index.ts index 1340792..9476a70 100644 --- a/packages/medusa-plugin-auth/src/types/index.ts +++ b/packages/medusa-plugin-auth/src/types/index.ts @@ -1,6 +1,7 @@ import { GoogleAuthOptions } from '../auth-strategies/google'; import { FacebookAuthOptions } from '../auth-strategies/facebook'; import { TwitterAuthOptions } from '../auth-strategies/twitter'; +import { LinkedinAuthOptions } from '../auth-strategies/linkedin'; export const AUTH_TOKEN_COOKIE_NAME = 'auth_token'; @@ -12,4 +13,5 @@ export type AuthOptions = { google?: GoogleAuthOptions; facebook?: FacebookAuthOptions; twitter?: TwitterAuthOptions; + linkedin?: LinkedinAuthOptions; }; diff --git a/yarn.lock b/yarn.lock index 58bc0f7..62c72c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13506,6 +13506,13 @@ passport-jwt@^4.0.0: jsonwebtoken "^8.2.0" passport-strategy "^1.0.0" +passport-linkedin-oauth2@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/passport-linkedin-oauth2/-/passport-linkedin-oauth2-2.0.0.tgz#c320374baa44c1412a9b023590d1d0c1c77cfe73" + integrity sha512-PnSeq2HzFQ/y1/p2RTF/kG2zhJ7kwGVg4xO3E+JNxz2aI0pFJGAqC503FVpUksYbhQdNhL6QYlK9qrEXD7ZYCg== + dependencies: + passport-oauth2 "1.x.x" + passport-local@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee" @@ -13513,15 +13520,6 @@ passport-local@^1.0.0: dependencies: passport-strategy "1.x.x" -passport-oauth1@1.x.x: - version "1.2.0" - resolved "https://registry.yarnpkg.com/passport-oauth1/-/passport-oauth1-1.2.0.tgz#5229d431781bf5b265bec86ce9a9cce58a756cf9" - integrity sha512-Sv2YWodC6jN12M/OXwmR4BIXeeIHjjbwYTQw4kS6tHK4zYzSEpxBgSJJnknBjICA5cj0ju3FSnG1XmHgIhYnLg== - dependencies: - oauth "0.9.x" - passport-strategy "1.x.x" - utils-merge "1.x.x" - passport-oauth2@1.x.x, passport-oauth2@^1.1.2, passport-oauth2@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.6.1.tgz#c5aee8f849ce8bd436c7f81d904a3cd1666f181b" @@ -13538,22 +13536,6 @@ passport-strategy@1.x.x, passport-strategy@^1.0.0: resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== -passport-twitter-oauth2@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/passport-twitter-oauth2/-/passport-twitter-oauth2-2.1.1.tgz#ab4a333aa4d75c4e72aeb026db4d92d33e48233b" - integrity sha512-wkJLfsms4xIS5MO9IK243rJI9cASJBma9CYJAnVf0alj4blp/wykeQrr0i0HgBwrtAiJUN/nqx3P9rzwS5ytAQ== - dependencies: - passport-oauth2 "1.x.x" - xtraverse "0.1.x" - -passport-twitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/passport-twitter/-/passport-twitter-1.0.4.tgz#01a799e1f760bf2de49f2ba5fba32282f18932d7" - integrity sha512-qvdauqCqCJJci82mJ9hZZQ6nAv7aSHV31svL8+9H7mRlDdXCdfU6AARQrmmJu3DRmv9fvIebM7zzxR7mVufN3A== - dependencies: - passport-oauth1 "1.x.x" - xtraverse "0.1.x" - passport@0.4.1, passport@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" @@ -17495,11 +17477,6 @@ xmlbuilder@~11.0.0: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== -xmldom@0.1.x: - version "0.1.31" - resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" - integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ== - xmlhttprequest-ssl@~1.6.2: version "1.6.3" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" @@ -17523,13 +17500,6 @@ xtend@^4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xtraverse@0.1.x: - version "0.1.0" - resolved "https://registry.yarnpkg.com/xtraverse/-/xtraverse-0.1.0.tgz#b741bad018ef78d8a9d2e83ade007b3f7959c732" - integrity sha512-MANQdlG2hl1nQobxz1Rv8hsS1RuBS0C1N6qTOupv+9vmfrReePdxhmB2ecYjvsp4stJ80HD7erjkoF1Hd/FK9A== - dependencies: - xmldom "0.1.x" - xxhash-wasm@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz#752398c131a4dd407b5132ba62ad372029be6f79"