diff --git a/.changeset/pink-dolls-kiss.md b/.changeset/pink-dolls-kiss.md new file mode 100644 index 0000000000..f70f944005 --- /dev/null +++ b/.changeset/pink-dolls-kiss.md @@ -0,0 +1,8 @@ +--- +"@medusajs/auth-github": patch +"@medusajs/auth-google": patch +"@medusajs/auth": patch +"@medusajs/types": patch +--- + +Add github auth provider diff --git a/packages/core/js-sdk/src/auth/index.ts b/packages/core/js-sdk/src/auth/index.ts index 07a45e5490..23c9b75828 100644 --- a/packages/core/js-sdk/src/auth/index.ts +++ b/packages/core/js-sdk/src/auth/index.ts @@ -30,15 +30,49 @@ export class Auth { } login = async ( + actor: string, + method: string, + payload: HttpTypes.AdminSignInWithEmailPassword | Record + ) => { + // There will either be token or location returned from the backend. + const { token, location } = await this.client.fetch<{ + token?: string + location?: string + }>(`/auth/${actor}/${method}`, { + method: "POST", + body: payload, + }) + + // In the case of an oauth login, we return the redirect location to the caller. + // They can decide if they do an immediate redirect or put it in an tag. + if (location) { + return { location } + } + + // By default we just set the token in memory, if configured to use sessions we convert it into session storage instead. + if (this.config?.auth?.type === "session") { + await this.client.fetch("/auth/session", { + method: "POST", + headers: { Authorization: `Bearer ${token}` }, + }) + } else { + this.client.setToken(token as string) + } + + return token + } + + // The callback expects all query parameters from the Oauth callback to be passed to the backend, and the provider is in charge of parsing and validating them + callback = async ( actor: "customer" | "user", method: "emailpass", - payload: HttpTypes.AdminSignInWithEmailPassword + query?: Record ) => { const { token } = await this.client.fetch<{ token: string }>( - `/auth/${actor}/${method}`, + `/auth/${actor}/${method}/callback`, { - method: "POST", - body: payload, + method: "GET", + query, } ) diff --git a/packages/core/types/src/auth/common/provider.ts b/packages/core/types/src/auth/common/provider.ts index 87e3b103d3..0cc313ffb6 100644 --- a/packages/core/types/src/auth/common/provider.ts +++ b/packages/core/types/src/auth/common/provider.ts @@ -33,15 +33,6 @@ export type AuthenticationResponse = { * specified location. */ location?: string - - /** - * Some authentication providers support redirecting to a specified URL on - * success. In those cases, the URL to redirect to is set in this field. - * - * So, if `success` is true, there's no `location` set, and this field - * is set, you can redirect to this URL. - */ - successRedirectUrl?: string } /** diff --git a/packages/core/types/src/auth/provider.ts b/packages/core/types/src/auth/provider.ts index c312d4a7f1..d0a5ba4a4e 100644 --- a/packages/core/types/src/auth/provider.ts +++ b/packages/core/types/src/auth/provider.ts @@ -13,6 +13,13 @@ export interface AuthIdentityProviderService { provider_metadata?: Record user_metadata?: Record }) => Promise + update: ( + entity_id: string, + data: { + provider_metadata?: Record + user_metadata?: Record + } + ) => Promise } /** diff --git a/packages/core/types/src/auth/providers/github.ts b/packages/core/types/src/auth/providers/github.ts new file mode 100644 index 0000000000..3ed9e65b89 --- /dev/null +++ b/packages/core/types/src/auth/providers/github.ts @@ -0,0 +1,5 @@ +export interface GithubAuthProviderOptions { + clientId: string + clientSecret: string + callbackUrl: string +} diff --git a/packages/core/types/src/auth/providers/google.ts b/packages/core/types/src/auth/providers/google.ts index 8d06a460b8..f484428310 100644 --- a/packages/core/types/src/auth/providers/google.ts +++ b/packages/core/types/src/auth/providers/google.ts @@ -1,6 +1,5 @@ export interface GoogleAuthProviderOptions { - clientID: string + clientId: string clientSecret: string - callbackURL: string - successRedirectUrl?: string + callbackUrl: string } diff --git a/packages/core/types/src/auth/providers/index.ts b/packages/core/types/src/auth/providers/index.ts index 9925b4a041..db1b259ca5 100644 --- a/packages/core/types/src/auth/providers/index.ts +++ b/packages/core/types/src/auth/providers/index.ts @@ -1,2 +1,3 @@ export * from "./emailpass" export * from "./google" +export * from "./github" diff --git a/packages/core/types/src/auth/service.ts b/packages/core/types/src/auth/service.ts index 0dbeac7f5d..4deebbbe79 100644 --- a/packages/core/types/src/auth/service.ts +++ b/packages/core/types/src/auth/service.ts @@ -46,12 +46,12 @@ export interface IAuthModuleService extends IModuleService { */ authenticate( provider: string, - providerData: AuthenticationInput, + providerData: AuthenticationInput ): Promise register( provider: string, - providerData: AuthenticationInput, + providerData: AuthenticationInput ): Promise /** @@ -76,7 +76,7 @@ export interface IAuthModuleService extends IModuleService { * `req` is an instance of the `MedusaRequest` object: * * ```ts - * const { success, authIdentity, error, successRedirectUrl } = + * const { success, authIdentity, error } = * await authModuleService.validateCallback("google", { * url: req.url, * headers: req.headers, diff --git a/packages/medusa/src/api/auth/[actor_type]/[auth_provider]/callback/route.ts b/packages/medusa/src/api/auth/[actor_type]/[auth_provider]/callback/route.ts index 82e44d01c1..e658d5761c 100644 --- a/packages/medusa/src/api/auth/[actor_type]/[auth_provider]/callback/route.ts +++ b/packages/medusa/src/api/auth/[actor_type]/[auth_provider]/callback/route.ts @@ -41,8 +41,10 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { protocol: req.protocol, } as AuthenticationInput - const { success, error, authIdentity, successRedirectUrl } = - await service.validateCallback(auth_provider, authData) + const { success, error, authIdentity } = await service.validateCallback( + auth_provider, + authData + ) const entityIdKey = `${actor_type}_id` const entityId = authIdentity?.app_metadata?.[entityIdKey] as @@ -71,13 +73,6 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { } ) - if (successRedirectUrl) { - const url = new URL(successRedirectUrl!) - url.searchParams.append("access_token", token) - - return res.redirect(url.toString()) - } - return res.json({ token }) } diff --git a/packages/medusa/src/api/auth/[actor_type]/[auth_provider]/route.ts b/packages/medusa/src/api/auth/[actor_type]/[auth_provider]/route.ts index 1143bf3454..94f197df36 100644 --- a/packages/medusa/src/api/auth/[actor_type]/[auth_provider]/route.ts +++ b/packages/medusa/src/api/auth/[actor_type]/[auth_provider]/route.ts @@ -47,8 +47,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { ) if (location) { - res.redirect(location) - return + return res.status(200).json({ location }) } if (success) { diff --git a/packages/modules/auth/src/services/auth-module.ts b/packages/modules/auth/src/services/auth-module.ts index 16631e906d..e39ca7fae7 100644 --- a/packages/modules/auth/src/services/auth-module.ts +++ b/packages/modules/auth/src/services/auth-module.ts @@ -282,6 +282,74 @@ export default class AuthModuleService createdAuthIdentity ) }, + update: async ( + entity_id: string, + data: { + provider_metadata?: Record + user_metadata?: Record + } + ) => { + const authIdentities = await this.authIdentityService_.list( + { + provider_identities: { + entity_id, + provider, + }, + }, + { + relations: ["provider_identities"], + } + ) + + if (!authIdentities.length) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `AuthIdentity with entity_id "${entity_id}" not found` + ) + } + + if (authIdentities.length > 1) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Multiple authIdentities found for entity_id "${entity_id}"` + ) + } + + const providerIdentityData = authIdentities[0].provider_identities.find( + (pi) => pi.provider === provider + ) + + if (!providerIdentityData) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `ProviderIdentity with entity_id "${entity_id}" not found` + ) + } + + const updatedProviderIdentity = + await this.providerIdentityService_.update({ + id: providerIdentityData.id, + ...data, + }) + + const serializedResponse = + await this.baseRepository_.serialize( + authIdentities[0] + ) + const serializedProviderIdentity = + await this.baseRepository_.serialize( + updatedProviderIdentity + ) + + serializedResponse.provider_identities = [ + ...(serializedResponse.provider_identities?.filter( + (p) => p.provider !== provider + ) ?? []), + serializedProviderIdentity, + ] + + return serializedResponse + }, } } } diff --git a/packages/modules/providers/auth-github/.gitignore b/packages/modules/providers/auth-github/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/providers/auth-github/README.md b/packages/modules/providers/auth-github/README.md new file mode 100644 index 0000000000..fae796ef8c --- /dev/null +++ b/packages/modules/providers/auth-github/README.md @@ -0,0 +1,11 @@ +## Testing + +In order to manually test the flow, you can do the following: + +1. Register a Github App - https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app +2. Go to the app, fetch the clientId and create a new clientSecret +3. Replace the values in the test with your credentials +4. Remove the `server.listen()` call +5. Run the tests, get the `location` value from the `authenticate` test, open the browser +6. Once redirected, copy the `code` param from the URL, and add it in one of the `callback` success tests +7. Once you run the tests, you should get back an access token and so on. diff --git a/packages/modules/providers/auth-github/integration-tests/__tests__/services.spec.ts b/packages/modules/providers/auth-github/integration-tests/__tests__/services.spec.ts new file mode 100644 index 0000000000..db09b44385 --- /dev/null +++ b/packages/modules/providers/auth-github/integration-tests/__tests__/services.spec.ts @@ -0,0 +1,246 @@ +import { generateJwtToken, MedusaError } from "@medusajs/utils" +import { GithubAuthService } from "../../src/services/github" +import { http, HttpResponse } from "msw" +import { setupServer } from "msw/node" + +jest.setTimeout(100000) + +const sampleIdPayload = { + login: "octocat", + id: 1, + node_id: "MDQ6VXNlcjE=", + avatar_url: "https://github.com/images/error/octocat_happy.gif", + gravatar_id: "", + url: "https://api.github.com/users/octocat", + name: "monalisa octocat", + company: "GitHub", + location: "San Francisco", + email: "octocat@github.com", + two_factor_authentication: true, +} + +const baseUrl = "https://someurl.com" + +// This is just a network-layer mocking, it doesn't start an actual server +const server = setupServer( + http.post( + "https://github.com/login/oauth/access_token", + async ({ request, params, cookies }) => { + const url = request.url + if ( + url === + "https://github.com/login/oauth/access_token?client_id=test&client_secret=test&code=invalid-code&redirect_uri=https%3A%2F%2Fsomeurl.com%2Fauth%2Fgithub%2Fcallback" + ) { + return new HttpResponse(null, { + status: 401, + statusText: "Unauthorized", + }) + } + + if ( + url === + "https://github.com/login/oauth/access_token?client_id=test&client_secret=test&code=valid-code&redirect_uri=https%3A%2F%2Fsomeurl.com%2Fauth%2Fgithub%2Fcallback" + ) { + return new HttpResponse( + JSON.stringify({ + access_token: "test", + expires_in: 3600, + refresh_token: "test", + refresh_token_expires_in: 7200, + }) + ) + } + } + ), + + http.get( + "https://api.github.com/user", + async ({ request, params, cookies }) => { + return new HttpResponse(JSON.stringify(sampleIdPayload), { + status: 200, + statusText: "OK", + }) + } + ), + + http.all("*", ({ request, params, cookies }) => { + return new HttpResponse(null, { + status: 404, + statusText: "Not Found", + }) + }) +) + +describe("Github auth provider", () => { + let githubService: GithubAuthService + beforeAll(() => { + githubService = new GithubAuthService( + { + logger: console as any, + }, + { + clientId: "test", + clientSecret: "test", + callbackUrl: `${baseUrl}/auth/github/callback`, + } + ) + + server.listen() + }) + + afterEach(() => { + server.resetHandlers() + jest.restoreAllMocks() + }) + + afterAll(() => server.close()) + + it("throw an error if required options are not passed", async () => { + let msg = "" + try { + GithubAuthService.validateOptions({ + clientId: "test", + clientSecret: "test", + } as any) + } catch (e) { + msg = e.message + } + + expect(msg).toEqual("Github callbackUrl is required") + }) + + it("returns a redirect URL on authenticate", async () => { + const res = await githubService.authenticate({}) + expect(res).toEqual({ + success: true, + location: + "https://github.com/login/oauth/authorize?redirect_uri=https%3A%2F%2Fsomeurl.com%2Fauth%2Fgithub%2Fcallback&client_id=test&response_type=code", + }) + }) + + it("validate callback should return an error on empty code", async () => { + const res = await githubService.validateCallback( + { + query: {}, + }, + {} as any + ) + expect(res).toEqual({ + success: false, + error: "No code provided", + }) + }) + + it("validate callback should return on a missing access token for code", async () => { + const res = await githubService.validateCallback( + { + query: { + code: "invalid-code", + }, + }, + {} as any + ) + + expect(res).toEqual({ + success: false, + error: "Could not exchange token, 401, Unauthorized", + }) + }) + + it("validate callback should return successfully on a correct code for a new user", async () => { + const authServiceSpies = { + retrieve: jest.fn().mockImplementation(() => { + throw new MedusaError(MedusaError.Types.NOT_FOUND, "Not found") + }), + create: jest.fn().mockImplementation(() => { + return { + provider_identities: [ + { + entity_id: "test@admin.com", + provider: "github", + }, + ], + } + }), + update: jest.fn().mockImplementation(() => { + throw new MedusaError(MedusaError.Types.NOT_FOUND, "Not found") + }), + } + + const res = await githubService.validateCallback( + { + query: { + code: "valid-code", + }, + }, + authServiceSpies + ) + + expect(res).toEqual({ + success: true, + authIdentity: { + provider_identities: [ + { + entity_id: "test@admin.com", + provider: "github", + }, + ], + }, + }) + }) + + it("validate callback should return successfully on a correct code for an existing user", async () => { + const authServiceSpies = { + retrieve: jest.fn().mockImplementation(() => { + return { + provider_identities: [ + { + entity_id: "test@admin.com", + provider: "github", + }, + ], + } + }), + create: jest.fn().mockImplementation(() => { + return {} + }), + update: jest.fn().mockImplementation(() => { + return { + provider_identities: [ + { + entity_id: "test@admin.com", + provider: "github", + provider_metadata: { + access_token: "test", + }, + }, + ], + } + }), + } + + const res = await githubService.validateCallback( + { + query: { + code: "valid-code", + }, + }, + authServiceSpies + ) + + expect(res).toEqual({ + success: true, + authIdentity: { + provider_identities: [ + { + entity_id: "test@admin.com", + provider: "github", + provider_metadata: { + access_token: "test", + }, + }, + ], + }, + }) + }) +}) diff --git a/packages/modules/providers/auth-github/jest.config.js b/packages/modules/providers/auth-github/jest.config.js new file mode 100644 index 0000000000..f33ccadd61 --- /dev/null +++ b/packages/modules/providers/auth-github/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + transform: { "^.+\\.[jt]s?$": "@swc/jest" }, + testEnvironment: `node`, + moduleFileExtensions: [`js`, `jsx`, `ts`, `tsx`, `json`], +} diff --git a/packages/modules/providers/auth-github/package.json b/packages/modules/providers/auth-github/package.json new file mode 100644 index 0000000000..7d7eeb5e2b --- /dev/null +++ b/packages/modules/providers/auth-github/package.json @@ -0,0 +1,41 @@ +{ + "name": "@medusajs/auth-github", + "version": "0.0.1", + "description": "Github OAuth authentication provider for Medusa", + "main": "dist/index.js", + "repository": { + "type": "git", + "url": "https://github.com/medusajs/medusa", + "directory": "packages/modules/providers/auth-github" + }, + "files": [ + "dist" + ], + "engines": { + "node": ">=20" + }, + "author": "Medusa", + "license": "MIT", + "scripts": { + "test": "jest --passWithNoTests src", + "test:integration": "jest --forceExit -- integration-tests/**/__tests__/**/*.spec.ts", + "build": "rimraf dist && tsc --build", + "watch": "tsc --watch" + }, + "devDependencies": { + "@medusajs/types": "^1.11.16", + "@types/simple-oauth2": "^5.0.7", + "cross-env": "^5.2.1", + "jest": "^29.7.0", + "msw": "^2.3.0", + "rimraf": "^5.0.1", + "typescript": "^5.1.6" + }, + "dependencies": { + "@medusajs/utils": "^1.11.9" + }, + "keywords": [ + "medusa-provider", + "medusa-provider-auth-github" + ] +} diff --git a/packages/modules/providers/auth-github/src/index.ts b/packages/modules/providers/auth-github/src/index.ts new file mode 100644 index 0000000000..6a4a0bcb9f --- /dev/null +++ b/packages/modules/providers/auth-github/src/index.ts @@ -0,0 +1,10 @@ +import { ModuleProviderExports } from "@medusajs/types" +import { GithubAuthService } from "./services/github" + +const services = [GithubAuthService] + +const providerExport: ModuleProviderExports = { + services, +} + +export default providerExport diff --git a/packages/modules/providers/auth-github/src/services/github.ts b/packages/modules/providers/auth-github/src/services/github.ts new file mode 100644 index 0000000000..9f3715c9b8 --- /dev/null +++ b/packages/modules/providers/auth-github/src/services/github.ts @@ -0,0 +1,203 @@ +import { + AuthenticationInput, + AuthenticationResponse, + AuthIdentityProviderService, + GithubAuthProviderOptions, + Logger, +} from "@medusajs/types" +import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils" + +type InjectedDependencies = { + logger: Logger +} + +interface LocalServiceConfig extends GithubAuthProviderOptions {} + +// TODO: Add state param that is stored in Redis, to prevent CSRF attacks +export class GithubAuthService extends AbstractAuthModuleProvider { + protected config_: LocalServiceConfig + protected logger_: Logger + + static validateOptions(options: GithubAuthProviderOptions) { + if (!options.clientId) { + throw new Error("Github clientId is required") + } + + if (!options.clientSecret) { + throw new Error("Github clientSecret is required") + } + + if (!options.callbackUrl) { + throw new Error("Github callbackUrl is required") + } + } + + constructor( + { logger }: InjectedDependencies, + options: GithubAuthProviderOptions + ) { + super({}, { provider: "github", displayName: "Github Authentication" }) + this.config_ = options + this.logger_ = logger + } + + async register(_): Promise { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + "Github does not support registration. Use method `authenticate` instead." + ) + } + + async authenticate( + req: AuthenticationInput + ): Promise { + if (req.query?.error) { + return { + success: false, + error: `${req.query.error_description}, read more at: ${req.query.error_uri}`, + } + } + + return this.getRedirect(this.config_) + } + + async validateCallback( + req: AuthenticationInput, + authIdentityService: AuthIdentityProviderService + ): Promise { + if (req.query && req.query.error) { + return { + success: false, + error: `${req.query.error_description}, read more at: ${req.query.error_uri}`, + } + } + + const code = req.query?.code ?? req.body?.code + if (!code) { + return { success: false, error: "No code provided" } + } + + const params = `client_id=${this.config_.clientId}&client_secret=${ + this.config_.clientSecret + }&code=${code}&redirect_uri=${encodeURIComponent(this.config_.callbackUrl)}` + + const exchangeTokenUrl = new URL( + `https://github.com/login/oauth/access_token?${params}` + ) + + try { + const response = await fetch(exchangeTokenUrl.toString(), { + method: "POST", + headers: { + Accept: "application/json", + }, + }).then((r) => { + if (!r.ok) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Could not exchange token, ${r.status}, ${r.statusText}` + ) + } + + return r.json() + }) + + const providerMetadata = { + access_token: response.access_token, + refresh_token: response.refresh_token, + // The response is in seconds + access_token_expires_at: new Date( + Date.now() + response.expires_in * 1000 + ).toISOString(), + refresh_token_expires_at: new Date( + Date.now() + response.refresh_token_expires_in * 1000 + ).toISOString(), + } + + const { authIdentity, success } = await this.upsert_( + providerMetadata, + authIdentityService + ) + + return { + success, + authIdentity, + } + } catch (error) { + return { success: false, error: error.message } + } + } + + async upsert_( + providerMetadata: { + access_token: string + refresh_token: string + access_token_expires_at: string + refresh_token_expires_at: string + }, + authIdentityService: AuthIdentityProviderService + ) { + if (!providerMetadata?.access_token) { + return { success: false, error: "No access token found" } + } + + const user = await fetch("https://api.github.com/user", { + headers: { + Accept: "application/json", + Authorization: `Bearer ${providerMetadata.access_token}`, + }, + }).then((r) => r.json()) + + const entity_id = user.id + const userMetadata = { + profile_url: user.url, + avatar: user.avatar_url, + email: user.email, + name: user.name, + company: user.company, + two_factor_authentication: user.two_factor_authentication ?? false, + } + + let authIdentity + + try { + // Update throws if auth identity not found + authIdentity = await authIdentityService.update(entity_id, { + provider_metadata: providerMetadata, + user_metadata: userMetadata, + }) + } catch (error) { + if (error.type === MedusaError.Types.NOT_FOUND) { + const createdAuthIdentity = await authIdentityService.create({ + entity_id, + user_metadata: userMetadata, + provider_metadata: providerMetadata, + }) + authIdentity = createdAuthIdentity + } else { + return { success: false, error: error.message } + } + } + + return { + success: true, + authIdentity, + } + } + + private getRedirect({ clientId, callbackUrl }: LocalServiceConfig) { + const redirectUrlParam = `redirect_uri=${encodeURIComponent(callbackUrl)}` + const clientIdParam = `client_id=${clientId}` + const responseTypeParam = "response_type=code" + + const authUrl = new URL( + `https://github.com/login/oauth/authorize?${[ + redirectUrlParam, + clientIdParam, + responseTypeParam, + ].join("&")}` + ) + + return { success: true, location: authUrl.toString() } + } +} diff --git a/packages/modules/providers/auth-github/tsconfig.json b/packages/modules/providers/auth-github/tsconfig.json new file mode 100644 index 0000000000..89a1f6bfc5 --- /dev/null +++ b/packages/modules/providers/auth-github/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "lib": ["es2021"], + "target": "es2021", + "outDir": "./dist", + "esModuleInterop": true, + "declarationMap": true, + "declaration": true, + "module": "commonjs", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "noImplicitReturns": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noImplicitThis": true, + "allowJs": true, + "skipLibCheck": true, + "inlineSourceMap": true /* Emit a single file with source maps instead of having a separate file. */ + }, + "include": ["src"], + "exclude": [ + "dist", + "build", + "src/**/__tests__", + "src/**/__mocks__", + "src/**/__fixtures__", + "node_modules", + ".eslintrc.js" + ] +} diff --git a/packages/modules/providers/auth-google/README.md b/packages/modules/providers/auth-google/README.md index df3785bb37..4620e76809 100644 --- a/packages/modules/providers/auth-google/README.md +++ b/packages/modules/providers/auth-google/README.md @@ -3,7 +3,7 @@ In order to manually test the flow, you can do the following: 1. Register an app in `https://console.cloud.google.com/apis/credentials/consent` -2. Generate clientID and clientSecret credentials in the console +2. Generate clientId and clientSecret credentials in the console 3. Replace the values in the test with your credentials 4. Remove the `server.listen()` call 5. Run the tests, get the `location` value from the `authenticate` test, open the browser diff --git a/packages/modules/providers/auth-google/integration-tests/__tests__/services.spec.ts b/packages/modules/providers/auth-google/integration-tests/__tests__/services.spec.ts index 8516f10e8c..613e1d9e36 100644 --- a/packages/modules/providers/auth-google/integration-tests/__tests__/services.spec.ts +++ b/packages/modules/providers/auth-google/integration-tests/__tests__/services.spec.ts @@ -78,10 +78,9 @@ describe("Google auth provider", () => { logger: console as any, }, { - clientID: "test", + clientId: "test", clientSecret: "test", - successRedirectUrl: baseUrl, - callbackURL: `${baseUrl}/auth/google/callback`, + callbackUrl: `${baseUrl}/auth/google/callback`, } ) @@ -99,7 +98,7 @@ describe("Google auth provider", () => { let msg = "" try { GoogleAuthService.validateOptions({ - clientID: "test", + clientId: "test", clientSecret: "test", } as any) } catch (e) { @@ -162,6 +161,9 @@ describe("Google auth provider", () => { ], } }), + update: jest.fn().mockImplementation(() => { + return {} + }), } const res = await googleService.validateCallback( @@ -175,7 +177,6 @@ describe("Google auth provider", () => { expect(res).toEqual({ success: true, - successRedirectUrl: baseUrl, authIdentity: { provider_identities: [ { @@ -202,6 +203,9 @@ describe("Google auth provider", () => { create: jest.fn().mockImplementation(() => { return {} }), + update: jest.fn().mockImplementation(() => { + return {} + }), } const res = await googleService.validateCallback( @@ -215,7 +219,6 @@ describe("Google auth provider", () => { expect(res).toEqual({ success: true, - successRedirectUrl: baseUrl, authIdentity: { provider_identities: [ { diff --git a/packages/modules/providers/auth-google/src/services/google.ts b/packages/modules/providers/auth-google/src/services/google.ts index 1c2ac7de4b..ecb4d672b3 100644 --- a/packages/modules/providers/auth-google/src/services/google.ts +++ b/packages/modules/providers/auth-google/src/services/google.ts @@ -20,15 +20,15 @@ export class GoogleAuthService extends AbstractAuthModuleProvider { protected logger_: Logger static validateOptions(options: GoogleAuthProviderOptions) { - if (!options.clientID) { - throw new Error("Google clientID is required") + if (!options.clientId) { + throw new Error("Google clientId is required") } if (!options.clientSecret) { throw new Error("Google clientSecret is required") } - if (!options.callbackURL) { + if (!options.callbackUrl) { throw new Error("Google callbackUrl is required") } } @@ -78,10 +78,10 @@ export class GoogleAuthService extends AbstractAuthModuleProvider { return { success: false, error: "No code provided" } } - const params = `client_id=${this.config_.clientID}&client_secret=${ + const params = `client_id=${this.config_.clientId}&client_secret=${ this.config_.clientSecret }&code=${code}&redirect_uri=${encodeURIComponent( - this.config_.callbackURL + this.config_.callbackUrl )}&grant_type=authorization_code` const exchangeTokenUrl = new URL( `https://oauth2.googleapis.com/token?${params}` @@ -109,7 +109,6 @@ export class GoogleAuthService extends AbstractAuthModuleProvider { return { success, authIdentity, - successRedirectUrl: this.config_.successRedirectUrl, } } catch (error) { return { success: false, error: error.message } @@ -169,9 +168,9 @@ export class GoogleAuthService extends AbstractAuthModuleProvider { } } - private getRedirect({ clientID, callbackURL }: LocalServiceConfig) { - const redirectUrlParam = `redirect_uri=${encodeURIComponent(callbackURL)}` - const clientIdParam = `client_id=${clientID}` + private getRedirect({ clientId, callbackUrl }: LocalServiceConfig) { + const redirectUrlParam = `redirect_uri=${encodeURIComponent(callbackUrl)}` + const clientIdParam = `client_id=${clientId}` const responseTypeParam = "response_type=code" const scopeParam = "scope=email+profile+openid" diff --git a/yarn.lock b/yarn.lock index 46c13b828d..b24bbaae81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4364,6 +4364,21 @@ __metadata: languageName: unknown linkType: soft +"@medusajs/auth-github@workspace:packages/modules/providers/auth-github": + version: 0.0.0-use.local + resolution: "@medusajs/auth-github@workspace:packages/modules/providers/auth-github" + dependencies: + "@medusajs/types": ^1.11.16 + "@medusajs/utils": ^1.11.9 + "@types/simple-oauth2": ^5.0.7 + cross-env: ^5.2.1 + jest: ^29.7.0 + msw: ^2.3.0 + rimraf: ^5.0.1 + typescript: ^5.1.6 + languageName: unknown + linkType: soft + "@medusajs/auth-google@workspace:packages/modules/providers/auth-google": version: 0.0.0-use.local resolution: "@medusajs/auth-google@workspace:packages/modules/providers/auth-google"