diff --git a/integration-tests/api/__tests__/admin/auth.js b/integration-tests/api/__tests__/admin/auth.js index 775a3b27ae..c9047e451e 100644 --- a/integration-tests/api/__tests__/admin/auth.js +++ b/integration-tests/api/__tests__/admin/auth.js @@ -33,7 +33,7 @@ medusaIntegrationTestRunner({ }) }, async () => { - return await api.post("/auth/admin/emailpass", { + return await api.post("/auth/user/emailpass", { email: "admin@medusa.js", password: "secret_password", }) @@ -63,7 +63,7 @@ medusaIntegrationTestRunner({ it("should test the entire authentication lifecycle", async () => { // sign in - const response = await api.post("/auth/admin/emailpass", { + const response = await api.post("/auth/user/emailpass", { email: "admin@medusa.js", password: "secret_password", }) diff --git a/integration-tests/api/__tests__/admin/user.js b/integration-tests/api/__tests__/admin/user.js index 4b512fec18..4f273bebf4 100644 --- a/integration-tests/api/__tests__/admin/user.js +++ b/integration-tests/api/__tests__/admin/user.js @@ -175,7 +175,7 @@ medusaIntegrationTestRunner({ token = await breaking( () => null, async () => { - const emailPassResponse = await api.post("/auth/admin/emailpass", { + const emailPassResponse = await api.post("/auth/user/emailpass", { email: "test@test123.com", password: "test123", }) @@ -230,7 +230,7 @@ medusaIntegrationTestRunner({ // V2 only test it.skip("should throw, if session/bearer auth is present for existing user", async () => { - const emailPassResponse = await api.post("/auth/admin/emailpass", { + const emailPassResponse = await api.post("/auth/user/emailpass", { email: "test@test123.com", password: "test123", }) diff --git a/integration-tests/api/medusa-config.js b/integration-tests/api/medusa-config.js index f7ae5882e3..0c516b1bf0 100644 --- a/integration-tests/api/medusa-config.js +++ b/integration-tests/api/medusa-config.js @@ -41,22 +41,7 @@ module.exports = { options: { ttl: cacheTTL }, }, workflows: true, - [Modules.AUTH]: { - scope: "internal", - resources: "shared", - resolve: "@medusajs/auth", - options: { - providers: [ - { - name: "emailpass", - scopes: { - admin: {}, - store: {}, - }, - }, - ], - }, - }, + [Modules.AUTH]: true, [Modules.USER]: { scope: "internal", resources: "shared", diff --git a/integration-tests/helpers/create-admin-user.ts b/integration-tests/helpers/create-admin-user.ts index 730ca44420..a7ccc72765 100644 --- a/integration-tests/helpers/create-admin-user.ts +++ b/integration-tests/helpers/create-admin-user.ts @@ -32,7 +32,6 @@ export const createAdminUser = async ( const authIdentity = await authModule.create({ provider: "emailpass", entity_id: "admin@medusa.js", - scope: "admin", provider_metadata: { password: "somepassword", }, @@ -55,8 +54,6 @@ export const createAdminUser = async ( actor_id: user.id, actor_type: "user", auth_identity_id: authIdentity.id, - scope: "admin", - app_metadata: {}, }, "test" ) diff --git a/integration-tests/modules/__tests__/auth/admin/email-password-provider.spec.ts b/integration-tests/modules/__tests__/auth/admin/email-password-provider.spec.ts index 227875a4ad..680e66f62f 100644 --- a/integration-tests/modules/__tests__/auth/admin/email-password-provider.spec.ts +++ b/integration-tests/modules/__tests__/auth/admin/email-password-provider.spec.ts @@ -43,14 +43,13 @@ medusaIntegrationTestRunner({ await authService.create({ provider: "emailpass", entity_id: email, - scope: "admin", provider_metadata: { password: passwordHash, }, }) const response = await api - .post(`/auth/admin/emailpass`, { + .post(`/auth/user/emailpass`, { email: email, password: password, }) @@ -75,14 +74,13 @@ medusaIntegrationTestRunner({ await authService.create({ provider: "emailpass", entity_id: email, - scope: "admin", provider_metadata: { password: passwordHash, }, }) const error = await api - .post(`/auth/admin/emailpass`, { + .post(`/auth/user/emailpass`, { email: email, password: "incorrect-password", }) @@ -101,7 +99,7 @@ medusaIntegrationTestRunner({ ).toString("base64") const error = await api - .post(`/auth/admin/emailpass`, { + .post(`/auth/user/emailpass`, { email: "should-not-exist", password: "should-not-exist", }) diff --git a/integration-tests/modules/__tests__/customer/store/create-customer.spec.ts b/integration-tests/modules/__tests__/customer/store/create-customer.spec.ts index 85295a8423..4b713080fa 100644 --- a/integration-tests/modules/__tests__/customer/store/create-customer.spec.ts +++ b/integration-tests/modules/__tests__/customer/store/create-customer.spec.ts @@ -38,7 +38,6 @@ medusaIntegrationTestRunner({ const authIdentity = await authService.create({ entity_id: "store_user", provider: "emailpass", - scope: "store", }) const token = jwt.sign(authIdentity, http.jwtSecret) diff --git a/integration-tests/modules/__tests__/invites/accept-invite.spec.ts b/integration-tests/modules/__tests__/invites/accept-invite.spec.ts index 20294aa35c..b51155ce56 100644 --- a/integration-tests/modules/__tests__/invites/accept-invite.spec.ts +++ b/integration-tests/modules/__tests__/invites/accept-invite.spec.ts @@ -27,7 +27,7 @@ medusaIntegrationTestRunner({ }) it("should fail to accept an invite with an invalid invite token", async () => { - const authResponse = await api.post(`/auth/admin/emailpass`, { + const authResponse = await api.post(`/auth/user/emailpass`, { email: "potential_member@test.com", password: "supersecret", }) @@ -58,7 +58,7 @@ medusaIntegrationTestRunner({ email: "potential_member@test.com", }) - const authResponse = await api.post(`/auth/admin/emailpass`, { + const authResponse = await api.post(`/auth/user/emailpass`, { email: "potential_member@test.com", password: "supersecret", }) @@ -92,7 +92,7 @@ medusaIntegrationTestRunner({ email: "potential_member@test.com", }) - const authResponse = await api.post(`/auth/admin/emailpass`, { + const authResponse = await api.post(`/auth/user/emailpass`, { email: "some-email@test.com", password: "supersecret", }) diff --git a/integration-tests/modules/helpers/create-authenticated-customer.ts b/integration-tests/modules/helpers/create-authenticated-customer.ts index 011ca9f689..ddef2cb00e 100644 --- a/integration-tests/modules/helpers/create-authenticated-customer.ts +++ b/integration-tests/modules/helpers/create-authenticated-customer.ts @@ -25,7 +25,6 @@ export const createAuthenticatedCustomer = async ( const authIdentity = await authService.create({ entity_id: "store_user", provider: "emailpass", - scope: "store", }) // Ideally we simulate a signup process than manually linking here. @@ -45,8 +44,6 @@ export const createAuthenticatedCustomer = async ( actor_id: customer.id, actor_type: "customer", auth_identity_id: authIdentity.id, - scope: "store", - app_metadata: {}, }, http.jwtSecret ) diff --git a/integration-tests/modules/medusa-config.js b/integration-tests/modules/medusa-config.js index 5ee43cba3a..c6f7b7e9e9 100644 --- a/integration-tests/modules/medusa-config.js +++ b/integration-tests/modules/medusa-config.js @@ -47,22 +47,7 @@ module.exports = { medusa_v2: enableMedusaV2, }, modules: { - [Modules.AUTH]: { - scope: "internal", - resources: "shared", - resolve: "@medusajs/auth", - options: { - providers: [ - { - name: "emailpass", - scopes: { - admin: {}, - store: {}, - }, - }, - ], - }, - }, + [Modules.AUTH]: true, [Modules.USER]: { scope: "internal", resources: "shared", diff --git a/integration-tests/scripts/cli/login.sh b/integration-tests/scripts/cli/login.sh index a4a99df646..9a2e1e310b 100755 --- a/integration-tests/scripts/cli/login.sh +++ b/integration-tests/scripts/cli/login.sh @@ -5,7 +5,7 @@ status_code=$(curl \ -H "Content-Type: application/json"\ -d '{"email":"'$1'", "password":"'$2'"}'\ --write-out %{http_code}\ - http://localhost:9000/auth/admin/emailpass) + http://localhost:9000/auth/user/emailpass) if [[ "$status_code" -ne 200 ]] ; then echo "Site status changed to $status_code" diff --git a/packages/admin-next/dashboard/src/hooks/api/auth.tsx b/packages/admin-next/dashboard/src/hooks/api/auth.tsx index 29271daf3b..3428798b97 100644 --- a/packages/admin-next/dashboard/src/hooks/api/auth.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/auth.tsx @@ -7,7 +7,7 @@ export const useEmailPassLogin = ( options?: UseMutationOptions ) => { return useMutation({ - mutationFn: (payload) => sdk.auth.login("admin", "emailpass", payload), + mutationFn: (payload) => sdk.auth.login("user", "emailpass", payload), onSuccess: async (data, variables, context) => { options?.onSuccess?.(data, variables, context) }, @@ -26,7 +26,7 @@ export const useCreateAuthUser = ( options?: UseMutationOptions<{ token: string }, Error, EmailPassReq> ) => { return useMutation({ - mutationFn: (payload) => sdk.auth.create("admin", "emailpass", payload), + mutationFn: (payload) => sdk.auth.create("user", "emailpass", payload), onSuccess: async (data, variables, context) => { options?.onSuccess?.(data, variables, context) }, diff --git a/packages/core/js-sdk/src/auth/index.ts b/packages/core/js-sdk/src/auth/index.ts index 5f844fa415..955d676b48 100644 --- a/packages/core/js-sdk/src/auth/index.ts +++ b/packages/core/js-sdk/src/auth/index.ts @@ -10,13 +10,13 @@ export class Auth { this.config = config } - public login = async ( - scope: "admin" | "store", + login = async ( + actor: "customer" | "user", method: "emailpass", payload: { email: string; password: string } ) => { const { token } = await this.client.fetch<{ token: string }>( - `/auth/${scope}/${method}`, + `/auth/${actor}/${method}`, { method: "POST", body: payload, @@ -45,11 +45,11 @@ export class Auth { } create = async ( - scope: "admin" | "store", + actor: "customer" | "user", method: "emailpass", payload: { email: string; password: string } ): Promise<{ token: string }> => { - return await this.client.fetch(`/auth/${scope}/${method}`, { + return await this.client.fetch(`/auth/${actor}/${method}`, { method: "POST", body: payload, }) diff --git a/packages/core/types/src/auth/common/auth-identity.ts b/packages/core/types/src/auth/common/auth-identity.ts index e6e9b60198..2ec0ce26ab 100644 --- a/packages/core/types/src/auth/common/auth-identity.ts +++ b/packages/core/types/src/auth/common/auth-identity.ts @@ -22,12 +22,6 @@ export type AuthIdentityDTO = { */ entity_id: string - /** - * The scope of the auth identity. For example, - * `admin` or `store`. - */ - scope: string - /** * Holds custom data related to the provider in key-value pairs. */ @@ -37,11 +31,6 @@ export type AuthIdentityDTO = { * Holds custom data related to the user in key-value pairs. */ user_metadata: Record - - /** - * Holds custom data related to the third-party app in key-value pairs. - */ - app_metadata: Record } /** @@ -67,12 +56,6 @@ export type CreateAuthIdentityDTO = { */ entity_id: string - /** - * The scope of the auth identity. For example, - * `admin` or `store`. - */ - scope: string - /** * Holds custom data related to the provider in key-value pairs. */ @@ -82,11 +65,6 @@ export type CreateAuthIdentityDTO = { * Holds custom data related to the user in key-value pairs. */ user_metadata?: Record - - /** - * Holds custom data related to the third-party app in key-value pairs. - */ - app_metadata?: Record } /** @@ -109,11 +87,6 @@ export type UpdateAuthIdentityDTO = { * Holds custom data related to the user in key-value pairs. */ user_metadata?: Record - - /** - * Holds custom data related to the third-party app in key-value pairs. - */ - app_metadata?: Record } /** diff --git a/packages/core/types/src/auth/common/provider.ts b/packages/core/types/src/auth/common/provider.ts index 2973d46be4..8300e27d02 100644 --- a/packages/core/types/src/auth/common/provider.ts +++ b/packages/core/types/src/auth/common/provider.ts @@ -42,31 +42,6 @@ export type AuthenticationResponse = { successRedirectUrl?: string } -/** - * @interface - * - * The configurations of the `providers` option - * passed to the Auth Module. - */ -export type AuthModuleProviderConfig = { - /** - * The provider's name. - */ - name: string - - /** - * The scopes configuration of that provider. - */ - scopes: Record -} - -/** - * @interface - * - * The scope configurations of an auth provider. - */ -export type AuthProviderScope = Record - /** * @interface * @@ -77,30 +52,25 @@ export type AuthenticationInput = { /** * URL of the incoming authentication request. */ - url: string + url?: string /** * Headers of incoming authentication request. */ - headers: Record + headers?: Record /** * Query params of the incoming authentication request. */ - query: Record + query?: Record /** * Body of the incoming authentication request. */ - body: Record - - /** - * Scope for the authentication request. - */ - authScope: string + body?: Record /** * Protocol of the incoming authentication request (For example, `https`). */ - protocol: string + protocol?: string } diff --git a/packages/core/types/src/auth/index.ts b/packages/core/types/src/auth/index.ts index 1aa665fd54..340f8c8015 100644 --- a/packages/core/types/src/auth/index.ts +++ b/packages/core/types/src/auth/index.ts @@ -1,2 +1,4 @@ export * from "./service" export * from "./common" +export * from "./provider" +export * from "./providers" diff --git a/packages/core/types/src/auth/provider.ts b/packages/core/types/src/auth/provider.ts new file mode 100644 index 0000000000..b028caa22b --- /dev/null +++ b/packages/core/types/src/auth/provider.ts @@ -0,0 +1,32 @@ +import { + AuthIdentityDTO, + AuthenticationInput, + AuthenticationResponse, + CreateAuthIdentityDTO, +} from "./common" + +export interface AuthIdentityProviderService { + // The provider is injected by the auth identity module + retrieve: (selector: { + entity_id: string + provider: string + }) => Promise + create: (data: CreateAuthIdentityDTO) => Promise +} + +/** + * ## Overview + * + * Authentication provider interface for the auth module. + * + */ +export interface IAuthProvider { + authenticate( + data: AuthenticationInput, + authIdentityProviderService: AuthIdentityProviderService + ): Promise + validateCallback( + data: AuthenticationInput, + authIdentityProviderService: AuthIdentityProviderService + ): Promise +} diff --git a/packages/core/types/src/auth/providers/emailpass.ts b/packages/core/types/src/auth/providers/emailpass.ts new file mode 100644 index 0000000000..bfada95435 --- /dev/null +++ b/packages/core/types/src/auth/providers/emailpass.ts @@ -0,0 +1,7 @@ +export interface EmailPassAuthProviderOptions { + hashConfig?: { + logN: number + r: number + p: number + } +} diff --git a/packages/core/types/src/auth/providers/index.ts b/packages/core/types/src/auth/providers/index.ts new file mode 100644 index 0000000000..5f6f11eafa --- /dev/null +++ b/packages/core/types/src/auth/providers/index.ts @@ -0,0 +1 @@ +export * from "./emailpass" diff --git a/packages/core/types/src/auth/service.ts b/packages/core/types/src/auth/service.ts index 94f0e7b882..01e0a314e7 100644 --- a/packages/core/types/src/auth/service.ts +++ b/packages/core/types/src/auth/service.ts @@ -43,7 +43,6 @@ export interface IAuthModuleService extends IModuleService { * headers: req.headers, * query: req.query, * body: req.body, - * authScope: "admin", * protocol: req.protocol, * } as AuthenticationInput) * ``` @@ -81,7 +80,6 @@ export interface IAuthModuleService extends IModuleService { * headers: req.headers, * query: req.query, * body: req.body, - * authScope: "admin", * protocol: req.protocol, * } as AuthenticationInput) * ``` @@ -200,12 +198,10 @@ export interface IAuthModuleService extends IModuleService { * { * provider: "emailpass", * entity_id: "user@example.com", - * scope: "admin", * }, * { * provider: "google", * entity_id: "user@gmail.com", - * scope: "email profile", * }, * ]) */ @@ -225,7 +221,6 @@ export interface IAuthModuleService extends IModuleService { * const authIdentity = await authModuleService.create({ * provider: "emailpass", * entity_id: "user@example.com", - * scope: "admin", * }) */ create( @@ -244,9 +239,6 @@ export interface IAuthModuleService extends IModuleService { * const authIdentities = await authModuleService.update([ * { * id: "authusr_123", - * app_metadata: { - * test: true, - * }, * }, * ]) */ @@ -265,9 +257,6 @@ export interface IAuthModuleService extends IModuleService { * @example * const authIdentity = await authModuleService.update({ * id: "authusr_123", - * app_metadata: { - * test: true, - * }, * }) */ update( diff --git a/packages/core/types/src/common/config-module.ts b/packages/core/types/src/common/config-module.ts index 9c97458620..3188096901 100644 --- a/packages/core/types/src/common/config-module.ts +++ b/packages/core/types/src/common/config-module.ts @@ -705,6 +705,35 @@ export type ProjectConfigOptions = { * ``` */ adminCors: string + + /** + * Optionally you can specify the supported authentication providers per actor type (such as user, customer, or any custom actors). + * For example, you only want to allow SSO logins for `users` to the admin, while you want to allow email/password logins for `customers` to the storefront. + * + * `authMethodsPerActor` is a a map where the actor type (eg. 'user') is the key, and an array of supported auth providers as the value. + * + * + * @example + * Some example values of common use cases: + * + * Then, set the configuration in `medusa-config.js`: + * + * ```js title="medusa-config.js" + * module.exports = { + * projectConfig: { + * http: { + * authMethodsPerActor: { + * user: ['sso'], + * customer: ["emailpass", "google"] + * }, + * }, + * // ... + * }, + * // ... + * } + * ``` + */ + authMethodsPerActor: Record } } diff --git a/packages/core/utils/src/auth/abstract-auth-provider.ts b/packages/core/utils/src/auth/abstract-auth-provider.ts index 619d1f9d38..45c2fac263 100644 --- a/packages/core/utils/src/auth/abstract-auth-provider.ts +++ b/packages/core/utils/src/auth/abstract-auth-provider.ts @@ -1,17 +1,15 @@ -import { AuthProviderScope, AuthenticationResponse } from "@medusajs/types" +import { + AuthIdentityProviderService, + AuthenticationInput, + AuthenticationResponse, + IAuthProvider, +} from "@medusajs/types" -import { MedusaError } from "../common" - -export abstract class AbstractAuthModuleProvider { - public static PROVIDER: string - public static DISPLAY_NAME: string +export abstract class AbstractAuthModuleProvider implements IAuthProvider { + private static PROVIDER: string + private static DISPLAY_NAME: string protected readonly container_: any - protected scopeConfig_: AuthProviderScope - protected scope_: string - - private readonly scopes_: Record - public get provider() { return (this.constructor as typeof AbstractAuthModuleProvider).PROVIDER } @@ -20,43 +18,22 @@ export abstract class AbstractAuthModuleProvider { return (this.constructor as typeof AbstractAuthModuleProvider).DISPLAY_NAME } - protected constructor( - { scopes }, - config: { provider: string; displayName: string } - ) { + protected constructor({}, config: { provider: string; displayName: string }) { this.container_ = arguments[0] - this.scopes_ = scopes ;(this.constructor as typeof AbstractAuthModuleProvider).PROVIDER ??= config.provider ;(this.constructor as typeof AbstractAuthModuleProvider).DISPLAY_NAME ??= config.displayName } - private validateScope(scope) { - if (!this.scopes_[scope]) { - throw new MedusaError( - MedusaError.Types.INVALID_ARGUMENT, - `Scope "${scope}" is not valid for provider ${this.provider}` - ) - } - } - - public withScope(scope: string) { - this.validateScope(scope) - - const cloned = new (this.constructor as any)(this.container_) - cloned.scope_ = scope - cloned.scopeConfig_ = this.scopes_[scope] - - return cloned - } - abstract authenticate( - data: Record + data: AuthenticationInput, + authIdentityProviderService: AuthIdentityProviderService ): Promise - public validateCallback( - data: Record + validateCallback( + data: AuthenticationInput, + authIdentityProviderService: AuthIdentityProviderService ): Promise { throw new Error( `Callback authentication not implemented for provider ${this.provider}` diff --git a/packages/medusa/src/api/admin/api-keys/middlewares.ts b/packages/medusa/src/api/admin/api-keys/middlewares.ts index 1993980313..352a4580e8 100644 --- a/packages/medusa/src/api/admin/api-keys/middlewares.ts +++ b/packages/medusa/src/api/admin/api-keys/middlewares.ts @@ -16,7 +16,7 @@ import { createLinkBody } from "../../utils/validators" export const adminApiKeyRoutesMiddlewares: MiddlewareRoute[] = [ { matcher: "/admin/api-keys*", - middlewares: [authenticate("admin", ["bearer", "session"])], + middlewares: [authenticate("user", ["bearer", "session"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/campaigns/middlewares.ts b/packages/medusa/src/api/admin/campaigns/middlewares.ts index 2a8c6f20da..6f0e317ff2 100644 --- a/packages/medusa/src/api/admin/campaigns/middlewares.ts +++ b/packages/medusa/src/api/admin/campaigns/middlewares.ts @@ -14,7 +14,7 @@ import { export const adminCampaignRoutesMiddlewares: MiddlewareRoute[] = [ { matcher: "/admin/campaigns*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/collections/middlewares.ts b/packages/medusa/src/api/admin/collections/middlewares.ts index 4a86b57445..f8ca442aa4 100644 --- a/packages/medusa/src/api/admin/collections/middlewares.ts +++ b/packages/medusa/src/api/admin/collections/middlewares.ts @@ -15,7 +15,7 @@ export const adminCollectionRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/collections*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { diff --git a/packages/medusa/src/api/admin/currencies/middlewares.ts b/packages/medusa/src/api/admin/currencies/middlewares.ts index ab49f12577..d29d6fff76 100644 --- a/packages/medusa/src/api/admin/currencies/middlewares.ts +++ b/packages/medusa/src/api/admin/currencies/middlewares.ts @@ -8,7 +8,7 @@ export const adminCurrencyRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/currencies*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/customer-groups/middlewares.ts b/packages/medusa/src/api/admin/customer-groups/middlewares.ts index 4356ee3de5..6b47141b23 100644 --- a/packages/medusa/src/api/admin/customer-groups/middlewares.ts +++ b/packages/medusa/src/api/admin/customer-groups/middlewares.ts @@ -15,7 +15,7 @@ export const adminCustomerGroupRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/customer-groups*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/customers/middlewares.ts b/packages/medusa/src/api/admin/customers/middlewares.ts index 30a1664060..c8a5a7586a 100644 --- a/packages/medusa/src/api/admin/customers/middlewares.ts +++ b/packages/medusa/src/api/admin/customers/middlewares.ts @@ -19,7 +19,7 @@ export const adminCustomerRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/customers*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/draft-orders/middlewares.ts b/packages/medusa/src/api/admin/draft-orders/middlewares.ts index 4964d2806c..421203f423 100644 --- a/packages/medusa/src/api/admin/draft-orders/middlewares.ts +++ b/packages/medusa/src/api/admin/draft-orders/middlewares.ts @@ -13,7 +13,7 @@ export const adminDraftOrderRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/draft-orders*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/fulfillment-providers/middlewares.ts b/packages/medusa/src/api/admin/fulfillment-providers/middlewares.ts index 9b42a3d2b1..079b7361af 100644 --- a/packages/medusa/src/api/admin/fulfillment-providers/middlewares.ts +++ b/packages/medusa/src/api/admin/fulfillment-providers/middlewares.ts @@ -8,7 +8,7 @@ export const adminFulfillmentProvidersRoutesMiddlewares: MiddlewareRoute[] = [ { method: "ALL", matcher: "/admin/fulfillment-providers*", - middlewares: [authenticate("admin", ["session", "bearer", "api-key"])], + middlewares: [authenticate("user", ["session", "bearer", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/fulfillment-sets/middlewares.ts b/packages/medusa/src/api/admin/fulfillment-sets/middlewares.ts index f4bbf073d9..1803840ada 100644 --- a/packages/medusa/src/api/admin/fulfillment-sets/middlewares.ts +++ b/packages/medusa/src/api/admin/fulfillment-sets/middlewares.ts @@ -14,7 +14,7 @@ export const adminFulfillmentSetsRoutesMiddlewares: MiddlewareRoute[] = [ { method: "ALL", matcher: "/admin/fulfillment-sets*", - middlewares: [authenticate("admin", ["session", "bearer", "api-key"])], + middlewares: [authenticate("user", ["session", "bearer", "api-key"])], }, { method: ["POST"], diff --git a/packages/medusa/src/api/admin/fulfillments/middlewares.ts b/packages/medusa/src/api/admin/fulfillments/middlewares.ts index ebcaf91fca..a2e6f06e7a 100644 --- a/packages/medusa/src/api/admin/fulfillments/middlewares.ts +++ b/packages/medusa/src/api/admin/fulfillments/middlewares.ts @@ -14,7 +14,7 @@ export const adminFulfillmentsRoutesMiddlewares: MiddlewareRoute[] = [ { method: "ALL", matcher: "/admin/fulfillments*", - middlewares: [authenticate("admin", ["session", "bearer", "api-key"])], + middlewares: [authenticate("user", ["session", "bearer", "api-key"])], }, { method: ["POST"], diff --git a/packages/medusa/src/api/admin/inventory-items/middlewares.ts b/packages/medusa/src/api/admin/inventory-items/middlewares.ts index bb6a336cc2..aedfe50da8 100644 --- a/packages/medusa/src/api/admin/inventory-items/middlewares.ts +++ b/packages/medusa/src/api/admin/inventory-items/middlewares.ts @@ -20,7 +20,7 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [ { method: "ALL", matcher: "/admin/inventory-items*", - middlewares: [authenticate("admin", ["session", "bearer", "api-key"])], + middlewares: [authenticate("user", ["session", "bearer", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/invites/middlewares.ts b/packages/medusa/src/api/admin/invites/middlewares.ts index b13f7591ab..729abc2ea6 100644 --- a/packages/medusa/src/api/admin/invites/middlewares.ts +++ b/packages/medusa/src/api/admin/invites/middlewares.ts @@ -18,7 +18,7 @@ export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [ method: ["GET"], matcher: "/admin/invites", middlewares: [ - authenticate("admin", ["session", "bearer", "api-key"]), + authenticate("user", ["session", "bearer", "api-key"]), validateAndTransformQuery( AdminGetInvitesParams, QueryConfig.listTransformQueryConfig @@ -29,7 +29,7 @@ export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [ method: ["POST"], matcher: "/admin/invites", middlewares: [ - authenticate("admin", ["session", "bearer", "api-key"]), + authenticate("user", ["session", "bearer", "api-key"]), validateAndTransformBody(AdminCreateInvite), validateAndTransformQuery( AdminGetInviteParams, @@ -41,7 +41,7 @@ export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [ method: "POST", matcher: "/admin/invites/accept", middlewares: [ - authenticate("admin", ["session", "bearer"], { + authenticate("user", ["session", "bearer"], { allowUnregistered: true, }), validateAndTransformBody(AdminInviteAccept), @@ -55,7 +55,7 @@ export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [ method: ["GET"], matcher: "/admin/invites/:id", middlewares: [ - authenticate("admin", ["session", "bearer", "api-key"]), + authenticate("user", ["session", "bearer", "api-key"]), validateAndTransformQuery( AdminGetInviteParams, QueryConfig.retrieveTransformQueryConfig @@ -65,13 +65,13 @@ export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["DELETE"], matcher: "/admin/invites/:id", - middlewares: [authenticate("admin", ["session", "bearer", "api-key"])], + middlewares: [authenticate("user", ["session", "bearer", "api-key"])], }, { method: "POST", matcher: "/admin/invites/:id/resend", middlewares: [ - authenticate("admin", ["session", "bearer", "api-key"]), + authenticate("user", ["session", "bearer", "api-key"]), validateAndTransformQuery( AdminGetInviteParams, QueryConfig.retrieveTransformQueryConfig diff --git a/packages/medusa/src/api/admin/orders/middlewares.ts b/packages/medusa/src/api/admin/orders/middlewares.ts index f599dcca00..6cf836f578 100644 --- a/packages/medusa/src/api/admin/orders/middlewares.ts +++ b/packages/medusa/src/api/admin/orders/middlewares.ts @@ -14,7 +14,7 @@ export const adminOrderRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/orders*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/payments/middlewares.ts b/packages/medusa/src/api/admin/payments/middlewares.ts index acdf36bdd8..1d07e6fc6e 100644 --- a/packages/medusa/src/api/admin/payments/middlewares.ts +++ b/packages/medusa/src/api/admin/payments/middlewares.ts @@ -16,7 +16,7 @@ export const adminPaymentRoutesMiddlewares: MiddlewareRoute[] = [ { method: "ALL", matcher: "/admin/payments", - middlewares: [authenticate("admin", ["session", "bearer", "api-key"])], + middlewares: [authenticate("user", ["session", "bearer", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/price-lists/middlewares.ts b/packages/medusa/src/api/admin/price-lists/middlewares.ts index 178e91f80c..e9dd188b15 100644 --- a/packages/medusa/src/api/admin/price-lists/middlewares.ts +++ b/packages/medusa/src/api/admin/price-lists/middlewares.ts @@ -18,7 +18,7 @@ export const adminPriceListsRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/price-lists*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/pricing/middlewares.ts b/packages/medusa/src/api/admin/pricing/middlewares.ts index 98732f27a2..0ef8189727 100644 --- a/packages/medusa/src/api/admin/pricing/middlewares.ts +++ b/packages/medusa/src/api/admin/pricing/middlewares.ts @@ -13,7 +13,7 @@ import { export const adminPricingRoutesMiddlewares: MiddlewareRoute[] = [ { matcher: "/admin/pricing*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/product-categories/middlewares.ts b/packages/medusa/src/api/admin/product-categories/middlewares.ts index e871e66612..13987ed383 100644 --- a/packages/medusa/src/api/admin/product-categories/middlewares.ts +++ b/packages/medusa/src/api/admin/product-categories/middlewares.ts @@ -15,7 +15,7 @@ export const adminProductCategoryRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/product-categories*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/product-types/middlewares.ts b/packages/medusa/src/api/admin/product-types/middlewares.ts index 55fd9534da..01d052a9f6 100644 --- a/packages/medusa/src/api/admin/product-types/middlewares.ts +++ b/packages/medusa/src/api/admin/product-types/middlewares.ts @@ -14,7 +14,7 @@ export const adminProductTypeRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/product-types/*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { diff --git a/packages/medusa/src/api/admin/products/middlewares.ts b/packages/medusa/src/api/admin/products/middlewares.ts index 1df9084c44..3108638528 100644 --- a/packages/medusa/src/api/admin/products/middlewares.ts +++ b/packages/medusa/src/api/admin/products/middlewares.ts @@ -28,7 +28,7 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/products*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/promotions/middlewares.ts b/packages/medusa/src/api/admin/promotions/middlewares.ts index d708b54cbc..828915f883 100644 --- a/packages/medusa/src/api/admin/promotions/middlewares.ts +++ b/packages/medusa/src/api/admin/promotions/middlewares.ts @@ -19,7 +19,7 @@ import { export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [ { matcher: "/admin/promotions*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/regions/middlewares.ts b/packages/medusa/src/api/admin/regions/middlewares.ts index 5ce7620c7d..8fa76eee97 100644 --- a/packages/medusa/src/api/admin/regions/middlewares.ts +++ b/packages/medusa/src/api/admin/regions/middlewares.ts @@ -14,7 +14,7 @@ export const adminRegionRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/regions*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/reservations/middlewares.ts b/packages/medusa/src/api/admin/reservations/middlewares.ts index 45cac0485e..4b7be77c1f 100644 --- a/packages/medusa/src/api/admin/reservations/middlewares.ts +++ b/packages/medusa/src/api/admin/reservations/middlewares.ts @@ -15,7 +15,7 @@ export const adminReservationRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/reservations*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/return-reasons/middlewares.ts b/packages/medusa/src/api/admin/return-reasons/middlewares.ts index 4e0eb73e76..fb58cf01ef 100644 --- a/packages/medusa/src/api/admin/return-reasons/middlewares.ts +++ b/packages/medusa/src/api/admin/return-reasons/middlewares.ts @@ -13,7 +13,7 @@ export const adminReturnReasonRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/return-reasons*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/returns/middlewares.ts b/packages/medusa/src/api/admin/returns/middlewares.ts index c0ce6fa8c6..7003dd771f 100644 --- a/packages/medusa/src/api/admin/returns/middlewares.ts +++ b/packages/medusa/src/api/admin/returns/middlewares.ts @@ -13,7 +13,7 @@ export const adminOrderRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/returns*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/sales-channels/middlewares.ts b/packages/medusa/src/api/admin/sales-channels/middlewares.ts index ac249fffa7..6c3c856965 100644 --- a/packages/medusa/src/api/admin/sales-channels/middlewares.ts +++ b/packages/medusa/src/api/admin/sales-channels/middlewares.ts @@ -16,7 +16,7 @@ export const adminSalesChannelRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/sales-channels*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/shipping-options/middlewares.ts b/packages/medusa/src/api/admin/shipping-options/middlewares.ts index 335ac3142e..ff6714a6f5 100644 --- a/packages/medusa/src/api/admin/shipping-options/middlewares.ts +++ b/packages/medusa/src/api/admin/shipping-options/middlewares.ts @@ -21,7 +21,7 @@ import { createBatchBody } from "../../utils/validators" export const adminShippingOptionRoutesMiddlewares: MiddlewareRoute[] = [ { matcher: "/admin/shipping-options*", - middlewares: [authenticate("admin", ["bearer", "session"])], + middlewares: [authenticate("user", ["bearer", "session"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/shipping-profiles/middlewares.ts b/packages/medusa/src/api/admin/shipping-profiles/middlewares.ts index 88a4b26ab8..d5337bdb3f 100644 --- a/packages/medusa/src/api/admin/shipping-profiles/middlewares.ts +++ b/packages/medusa/src/api/admin/shipping-profiles/middlewares.ts @@ -15,7 +15,7 @@ import { export const adminShippingProfilesMiddlewares: MiddlewareRoute[] = [ { matcher: "/admin/shipping-profiles*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["POST"], diff --git a/packages/medusa/src/api/admin/stock-locations/middlewares.ts b/packages/medusa/src/api/admin/stock-locations/middlewares.ts index cc106409a1..47be848d4b 100644 --- a/packages/medusa/src/api/admin/stock-locations/middlewares.ts +++ b/packages/medusa/src/api/admin/stock-locations/middlewares.ts @@ -17,7 +17,7 @@ export const adminStockLocationRoutesMiddlewares: MiddlewareRoute[] = [ { method: "ALL", matcher: "/admin/stock-locations*", - middlewares: [authenticate("admin", ["session", "bearer", "api-key"])], + middlewares: [authenticate("user", ["session", "bearer", "api-key"])], }, { method: ["POST"], diff --git a/packages/medusa/src/api/admin/stores/middlewares.ts b/packages/medusa/src/api/admin/stores/middlewares.ts index b128c13b71..2cbe8ba001 100644 --- a/packages/medusa/src/api/admin/stores/middlewares.ts +++ b/packages/medusa/src/api/admin/stores/middlewares.ts @@ -13,7 +13,7 @@ export const adminStoreRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/stores*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/admin/tax-rates/middlewares.ts b/packages/medusa/src/api/admin/tax-rates/middlewares.ts index 84a5928f42..d637b72295 100644 --- a/packages/medusa/src/api/admin/tax-rates/middlewares.ts +++ b/packages/medusa/src/api/admin/tax-rates/middlewares.ts @@ -17,7 +17,7 @@ export const adminTaxRateRoutesMiddlewares: MiddlewareRoute[] = [ { method: "ALL", matcher: "/admin/tax-rates*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: "POST", diff --git a/packages/medusa/src/api/admin/tax-regions/middlewares.ts b/packages/medusa/src/api/admin/tax-regions/middlewares.ts index 4bf30a9aba..ba800c7274 100644 --- a/packages/medusa/src/api/admin/tax-regions/middlewares.ts +++ b/packages/medusa/src/api/admin/tax-regions/middlewares.ts @@ -15,7 +15,7 @@ export const adminTaxRegionRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/tax-regions*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: "POST", diff --git a/packages/medusa/src/api/admin/uploads/middlewares.ts b/packages/medusa/src/api/admin/uploads/middlewares.ts index c4a5eba550..973c3faf1c 100644 --- a/packages/medusa/src/api/admin/uploads/middlewares.ts +++ b/packages/medusa/src/api/admin/uploads/middlewares.ts @@ -14,7 +14,7 @@ export const adminUploadRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/uploads*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, // TODO: There is a `/protected` route in v1 that might need a bit more thought when implementing { diff --git a/packages/medusa/src/api/admin/users/middlewares.ts b/packages/medusa/src/api/admin/users/middlewares.ts index 261e0f4303..175fc4be09 100644 --- a/packages/medusa/src/api/admin/users/middlewares.ts +++ b/packages/medusa/src/api/admin/users/middlewares.ts @@ -17,7 +17,7 @@ export const adminUserRoutesMiddlewares: MiddlewareRoute[] = [ method: ["GET"], matcher: "/admin/users", middlewares: [ - authenticate("admin", ["bearer", "session"]), + authenticate("user", ["bearer", "session"]), validateAndTransformQuery( AdminGetUsersParams, QueryConfig.listTransformQueryConfig @@ -28,7 +28,7 @@ export const adminUserRoutesMiddlewares: MiddlewareRoute[] = [ method: ["POST"], matcher: "/admin/users", middlewares: [ - authenticate("admin", ["bearer", "session"], { allowUnregistered: true }), + authenticate("user", ["bearer", "session"], { allowUnregistered: true }), validateAndTransformBody(AdminCreateUser), validateAndTransformQuery( AdminGetUserParams, @@ -40,7 +40,7 @@ export const adminUserRoutesMiddlewares: MiddlewareRoute[] = [ method: ["GET"], matcher: "/admin/users/:id", middlewares: [ - authenticate("admin", ["bearer", "session"]), + authenticate("user", ["bearer", "session"]), validateAndTransformQuery( AdminGetUserParams, QueryConfig.retrieveTransformQueryConfig @@ -51,7 +51,7 @@ export const adminUserRoutesMiddlewares: MiddlewareRoute[] = [ method: ["GET"], matcher: "/admin/users/me", middlewares: [ - authenticate("admin", ["bearer", "session"]), + authenticate("user", ["bearer", "session"]), validateAndTransformQuery( AdminGetUserParams, QueryConfig.retrieveTransformQueryConfig @@ -62,7 +62,7 @@ export const adminUserRoutesMiddlewares: MiddlewareRoute[] = [ method: ["POST"], matcher: "/admin/users/:id", middlewares: [ - authenticate("admin", ["bearer", "session"]), + authenticate("user", ["bearer", "session"]), validateAndTransformBody(AdminUpdateUser), validateAndTransformQuery( AdminGetUserParams, @@ -73,6 +73,6 @@ export const adminUserRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["DELETE"], matcher: "/admin/users/:id", - middlewares: [authenticate("admin", ["bearer", "session"])], + middlewares: [authenticate("user", ["bearer", "session"])], }, ] diff --git a/packages/medusa/src/api/admin/users/route.ts b/packages/medusa/src/api/admin/users/route.ts index 0de8a754b0..4a9a2b97bf 100644 --- a/packages/medusa/src/api/admin/users/route.ts +++ b/packages/medusa/src/api/admin/users/route.ts @@ -73,8 +73,6 @@ export const POST = async ( actor_id: result.id, actor_type: "user", auth_identity_id: req.auth_context.auth_identity_id, - app_metadata: {}, - scope: "admin", }, { secret: jwtSecret, diff --git a/packages/medusa/src/api/admin/workflows-executions/middlewares.ts b/packages/medusa/src/api/admin/workflows-executions/middlewares.ts index 43558acf4d..4a00fd8dc5 100644 --- a/packages/medusa/src/api/admin/workflows-executions/middlewares.ts +++ b/packages/medusa/src/api/admin/workflows-executions/middlewares.ts @@ -16,7 +16,7 @@ export const adminWorkflowsExecutionsMiddlewares: MiddlewareRoute[] = [ { method: ["ALL"], matcher: "/admin/workflows-executions*", - middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + middlewares: [authenticate("user", ["bearer", "session", "api-key"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/auth/[scope]/[auth_provider]/callback/route.ts b/packages/medusa/src/api/auth/[actor_type]/[auth_provider]/callback/route.ts similarity index 69% rename from packages/medusa/src/api/auth/[scope]/[auth_provider]/callback/route.ts rename to packages/medusa/src/api/auth/[actor_type]/[auth_provider]/callback/route.ts index d251752fb8..a34549c39e 100644 --- a/packages/medusa/src/api/auth/[scope]/[auth_provider]/callback/route.ts +++ b/packages/medusa/src/api/auth/[actor_type]/[auth_provider]/callback/route.ts @@ -1,5 +1,9 @@ import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { AuthenticationInput, IAuthModuleService } from "@medusajs/types" +import { + AuthenticationInput, + IAuthModuleService, + ConfigModule, +} from "@medusajs/types" import { ContainerRegistrationKeys, MedusaError, @@ -9,10 +13,23 @@ import { MedusaRequest, MedusaResponse } from "../../../../../types/routing" import { generateJwtToken } from "../../../../utils/auth/token" export const GET = async (req: MedusaRequest, res: MedusaResponse) => { - const { scope, auth_provider } = req.params - const actorType = scope === "admin" ? "user" : "customer" - + const { actor_type, auth_provider } = req.params const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + const config: ConfigModule = req.scope.resolve( + ContainerRegistrationKeys.CONFIG_MODULE + ) + + const authMethods = (config.projectConfig?.http as any)?.authMethods ?? {} + // Not having the config defined would allow for all auth providers for the particular actor. + if (authMethods[actor_type]) { + if (!authMethods[actor_type].includes(auth_provider)) { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + `The actor type ${actor_type} is not allowed to use the auth provider ${auth_provider}` + ) + } + } + const service: IAuthModuleService = req.scope.resolve( ModuleRegistrationName.AUTH ) @@ -22,7 +39,6 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { headers: req.headers, query: req.query, body: req.body, - authScope: scope, protocol: req.protocol, } as AuthenticationInput @@ -31,11 +47,11 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { const queryObject = remoteQueryObjectFromString({ entryPoint: "auth_identity", - fields: [`${actorType}.id`], + fields: [`${actor_type}.id`], variables: { id: authIdentity.id }, }) const [actorData] = await remoteQuery(queryObject) - const entityId = actorData?.[actorType]?.id + const entityId = actorData?.[actor_type]?.id if (success) { const { http } = req.scope.resolve( @@ -43,14 +59,11 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { ).projectConfig const { jwtSecret, jwtExpiresIn } = http - // TODO: Clean up mapping between scope and actor type const token = generateJwtToken( { actor_id: entityId, - actor_type: actorType, + actor_type, auth_identity_id: authIdentity.id, - app_metadata: {}, - scope, }, { secret: jwtSecret, diff --git a/packages/medusa/src/api/auth/[scope]/[auth_provider]/route.ts b/packages/medusa/src/api/auth/[actor_type]/[auth_provider]/route.ts similarity index 68% rename from packages/medusa/src/api/auth/[scope]/[auth_provider]/route.ts rename to packages/medusa/src/api/auth/[actor_type]/[auth_provider]/route.ts index 365f0d6bef..f03e660d76 100644 --- a/packages/medusa/src/api/auth/[scope]/[auth_provider]/route.ts +++ b/packages/medusa/src/api/auth/[actor_type]/[auth_provider]/route.ts @@ -1,5 +1,9 @@ import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { AuthenticationInput, IAuthModuleService } from "@medusajs/types" +import { + AuthenticationInput, + IAuthModuleService, + ConfigModule, +} from "@medusajs/types" import { ContainerRegistrationKeys, MedusaError, @@ -9,9 +13,22 @@ import { MedusaRequest, MedusaResponse } from "../../../../types/routing" import { generateJwtToken } from "../../../utils/auth/token" export const GET = async (req: MedusaRequest, res: MedusaResponse) => { - const { scope, auth_provider } = req.params - const actorType = scope === "admin" ? "user" : "customer" + const { actor_type, auth_provider } = req.params const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + const config: ConfigModule = req.scope.resolve( + ContainerRegistrationKeys.CONFIG_MODULE + ) + + const authMethods = (config.projectConfig?.http as any)?.authMethods ?? {} + // Not having the config defined would allow for all auth providers for the particular actor. + if (authMethods[actor_type]) { + if (!authMethods[actor_type].includes(auth_provider)) { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + `The actor type ${actor_type} is not allowed to use the auth provider ${auth_provider}` + ) + } + } const service: IAuthModuleService = req.scope.resolve( ModuleRegistrationName.AUTH @@ -22,7 +39,6 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { headers: req.headers, query: req.query, body: req.body, - authScope: scope, protocol: req.protocol, } as AuthenticationInput @@ -43,21 +59,18 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { const queryObject = remoteQueryObjectFromString({ entryPoint: "auth_identity", - fields: [`${actorType}.id`], + fields: [`${actor_type}.id`], variables: { id: authIdentity.id }, }) const [actorData] = await remoteQuery(queryObject) - const entityId = actorData?.[actorType]?.id + const entityId = actorData?.[actor_type]?.id const { jwtSecret, jwtExpiresIn } = http - // TODO: Clean up mapping between scope and actor type const token = generateJwtToken( { actor_id: entityId, - actor_type: actorType, + actor_type, auth_identity_id: authIdentity.id, - app_metadata: {}, - scope, }, { secret: jwtSecret, diff --git a/packages/medusa/src/api/auth/middlewares.ts b/packages/medusa/src/api/auth/middlewares.ts index 57a7735800..1e573a0123 100644 --- a/packages/medusa/src/api/auth/middlewares.ts +++ b/packages/medusa/src/api/auth/middlewares.ts @@ -14,12 +14,12 @@ export const authRoutesMiddlewares: MiddlewareRoute[] = [ }, { method: ["POST"], - matcher: "/auth/:scope/:auth_provider/callback", + matcher: "/auth/:actor_type/:auth_provider/callback", middlewares: [], }, { method: ["POST"], - matcher: "/auth/:scope/:auth_provider", + matcher: "/auth/:actor_type/:auth_provider", middlewares: [], }, ] diff --git a/packages/medusa/src/api/store/carts/middlewares.ts b/packages/medusa/src/api/store/carts/middlewares.ts index d6c8c6b74f..f4fe64b5bc 100644 --- a/packages/medusa/src/api/store/carts/middlewares.ts +++ b/packages/medusa/src/api/store/carts/middlewares.ts @@ -25,7 +25,7 @@ export const storeCartRoutesMiddlewares: MiddlewareRoute[] = [ method: "ALL", matcher: "/store/carts*", middlewares: [ - authenticate("store", ["session", "bearer"], { + authenticate("customer", ["session", "bearer"], { allowUnauthenticated: true, }), ], diff --git a/packages/medusa/src/api/store/customers/middlewares.ts b/packages/medusa/src/api/store/customers/middlewares.ts index a8b72f8793..628a356825 100644 --- a/packages/medusa/src/api/store/customers/middlewares.ts +++ b/packages/medusa/src/api/store/customers/middlewares.ts @@ -20,7 +20,9 @@ export const storeCustomerRoutesMiddlewares: MiddlewareRoute[] = [ method: ["POST"], matcher: "/store/customers", middlewares: [ - authenticate("store", ["session", "bearer"], { allowUnregistered: true }), + authenticate("customer", ["session", "bearer"], { + allowUnregistered: true, + }), validateAndTransformBody(StoreCreateCustomer), validateAndTransformQuery( StoreGetCustomerParams, @@ -31,7 +33,7 @@ export const storeCustomerRoutesMiddlewares: MiddlewareRoute[] = [ { method: "ALL", matcher: "/store/customers/me*", - middlewares: [authenticate("store", ["session", "bearer"])], + middlewares: [authenticate("customer", ["session", "bearer"])], }, { method: ["GET"], diff --git a/packages/medusa/src/api/store/customers/route.ts b/packages/medusa/src/api/store/customers/route.ts index eed52946a0..33c12fa3ba 100644 --- a/packages/medusa/src/api/store/customers/route.ts +++ b/packages/medusa/src/api/store/customers/route.ts @@ -41,8 +41,6 @@ export const POST = async ( actor_id: result.id, actor_type: "customer", auth_identity_id: req.auth_context.auth_identity_id, - app_metadata: {}, - scope: "store", }, { secret: jwtSecret, diff --git a/packages/medusa/src/api/store/orders/middlewares.ts b/packages/medusa/src/api/store/orders/middlewares.ts index c5bf47ab0b..0504fce9bf 100644 --- a/packages/medusa/src/api/store/orders/middlewares.ts +++ b/packages/medusa/src/api/store/orders/middlewares.ts @@ -9,7 +9,7 @@ export const storeOrderRoutesMiddlewares: MiddlewareRoute[] = [ method: ["GET"], matcher: "/store/orders", middlewares: [ - authenticate("store", ["session", "bearer"]), + authenticate("customer", ["session", "bearer"]), validateAndTransformQuery( StoreGetOrdersParams, QueryConfig.listTransformQueryConfig @@ -20,7 +20,7 @@ export const storeOrderRoutesMiddlewares: MiddlewareRoute[] = [ method: ["GET"], matcher: "/store/orders/:id", middlewares: [ - authenticate("store", ["session", "bearer"], { + authenticate("customer", ["session", "bearer"], { allowUnauthenticated: true, }), validateAndTransformQuery( diff --git a/packages/medusa/src/api/store/payment-collections/middlewares.ts b/packages/medusa/src/api/store/payment-collections/middlewares.ts index 153b62cd66..3f0b15ce73 100644 --- a/packages/medusa/src/api/store/payment-collections/middlewares.ts +++ b/packages/medusa/src/api/store/payment-collections/middlewares.ts @@ -14,7 +14,7 @@ export const storePaymentCollectionsMiddlewares: MiddlewareRoute[] = [ method: "ALL", matcher: "/store/payment-collections*", middlewares: [ - authenticate("store", ["session", "bearer"], { + authenticate("customer", ["session", "bearer"], { allowUnauthenticated: true, }), ], diff --git a/packages/medusa/src/api/store/products/middlewares.ts b/packages/medusa/src/api/store/products/middlewares.ts index 8527265fc5..a1aab5747f 100644 --- a/packages/medusa/src/api/store/products/middlewares.ts +++ b/packages/medusa/src/api/store/products/middlewares.ts @@ -19,7 +19,7 @@ export const storeProductRoutesMiddlewares: MiddlewareRoute[] = [ method: "ALL", matcher: "/store/products*", middlewares: [ - authenticate("store", ["session", "bearer"], { + authenticate("customer", ["session", "bearer"], { allowUnauthenticated: true, }), ], diff --git a/packages/medusa/src/commands/user.js b/packages/medusa/src/commands/user.js index ede98a7241..86eb4b3090 100644 --- a/packages/medusa/src/commands/user.js +++ b/packages/medusa/src/commands/user.js @@ -46,14 +46,18 @@ export default async function ({ } else { const user = await userService.create({ email }) - const { authIdentity } = await authService.authenticate(provider, { + const { authIdentity, error } = await authService.authenticate(provider, { body: { email, password, }, - authScope: "admin", }) + if (error) { + Logger.error(error) + throw new Error(error) + } + await remoteLink.create([ { [Modules.USER]: { diff --git a/packages/medusa/src/types/routing.ts b/packages/medusa/src/types/routing.ts index 33425304d3..95e151487a 100644 --- a/packages/medusa/src/types/routing.ts +++ b/packages/medusa/src/types/routing.ts @@ -61,11 +61,8 @@ export interface MedusaRequest export interface AuthContext { actor_id: string - // TODO: We possibly want to make this more open-ended so it's easy to extend. - actor_type: "api-key" | "user" | "customer" | "unknown" + actor_type: string auth_identity_id: string - scope: string - app_metadata: Record } export interface AuthenticatedMedusaRequest diff --git a/packages/medusa/src/utils/middlewares/authenticate-middleware.ts b/packages/medusa/src/utils/middlewares/authenticate-middleware.ts index 1bba1484f2..7a8da1b3a6 100644 --- a/packages/medusa/src/utils/middlewares/authenticate-middleware.ts +++ b/packages/medusa/src/utils/middlewares/authenticate-middleware.ts @@ -8,25 +8,23 @@ import { MedusaRequest, MedusaResponse, } from "../../types/routing" +import { ContainerRegistrationKeys } from "@medusajs/utils" const SESSION_AUTH = "session" const BEARER_AUTH = "bearer" const API_KEY_AUTH = "api-key" +// This is the only hard-coded actor type, as API keys have special handling for now. We could also generalize API keys to carry the actor type with them. +const ADMIN_ACTOR_TYPE = "user" + type AuthType = typeof SESSION_AUTH | typeof BEARER_AUTH | typeof API_KEY_AUTH -const ADMIN_SCOPE = "admin" -const STORE_SCOPE = "store" -const ALL_SCOPE = "*" - -type Scope = typeof ADMIN_SCOPE | typeof STORE_SCOPE | typeof ALL_SCOPE - type MedusaSession = { auth_context: AuthContext } export const authenticate = ( - authScope: Scope | Scope[], + actorType: string, authType: AuthType | AuthType[], options: { allowUnauthenticated?: boolean; allowUnregistered?: boolean } = {} ): RequestHandler => { @@ -36,21 +34,20 @@ export const authenticate = ( next: NextFunction ): Promise => { const authTypes = Array.isArray(authType) ? authType : [authType] - const scopes = Array.isArray(authScope) ? authScope : [authScope] + const actorTypes = Array.isArray(actorType) ? actorType : [actorType] const req_ = req as AuthenticatedMedusaRequest // We only allow authenticating using a secret API key on the admin - const isExclusivelyAdmin = - scopes.length === 1 && scopes.includes(ADMIN_SCOPE) - if (authTypes.includes(API_KEY_AUTH) && isExclusivelyAdmin) { + const isExclusivelyUser = + actorTypes.length === 1 && actorTypes[0] === ADMIN_ACTOR_TYPE + + if (authTypes.includes(API_KEY_AUTH) && isExclusivelyUser) { const apiKey = await getApiKeyInfo(req) if (apiKey) { req_.auth_context = { actor_id: apiKey.id, actor_type: "api-key", auth_identity_id: "", - app_metadata: {}, - scope: ADMIN_SCOPE, } return next() @@ -61,27 +58,29 @@ export const authenticate = ( let authContext: AuthContext | null = getAuthContextFromSession( req.session, authTypes, - scopes + actorTypes ) if (!authContext) { - const { http } = - req.scope.resolve("configModule").projectConfig + const { http } = req.scope.resolve( + ContainerRegistrationKeys.CONFIG_MODULE + ).projectConfig + authContext = getAuthContextFromJwtToken( req.headers.authorization, http.jwtSecret!, authTypes, - scopes + actorTypes ) } - // If the entity is authenticated, and it is a registered user/customer we can continue - if (!!authContext?.actor_id && authContext.actor_type !== "unknown") { + // If the entity is authenticated, and it is a registered actor we can continue + if (authContext?.actor_id) { req_.auth_context = authContext return next() } - // If the entity is authenticated, but there is no user/customer yet, we can continue (eg. in the case of a user invite) if allow unregistered is set + // If the entity is authenticated, but there is no registered actor yet, we can continue (eg. in the case of a user invite) if allow unregistered is set if (authContext?.auth_identity_id && options.allowUnregistered) { req_.auth_context = authContext return next() @@ -142,7 +141,7 @@ const getApiKeyInfo = async (req: MedusaRequest): Promise => { const getAuthContextFromSession = ( session: Partial = {}, authTypes: AuthType[], - scopes: Scope[] + actorTypes: string[] ): AuthContext | null => { if (!authTypes.includes(SESSION_AUTH)) { return null @@ -150,8 +149,8 @@ const getAuthContextFromSession = ( if ( session.auth_context && - (scopes.includes("*") || - scopes.includes(session.auth_context.scope as Scope)) + (actorTypes.includes("*") || + actorTypes.includes(session.auth_context.actor_type)) ) { return session.auth_context } @@ -163,7 +162,7 @@ const getAuthContextFromJwtToken = ( authHeader: string | undefined, jwtSecret: string, authTypes: AuthType[], - scopes: Scope[] + actorTypes: string[] ): AuthContext | null => { if (!authTypes.includes(BEARER_AUTH)) { return null @@ -185,7 +184,10 @@ const getAuthContextFromJwtToken = ( // verify token and set authUser try { const verified = jwt.verify(token, jwtSecret) as JwtPayload - if (scopes.includes("*") || scopes.includes(verified.scope)) { + if ( + actorTypes.includes("*") || + actorTypes.includes(verified.actor_type) + ) { return verified as AuthContext } } catch (err) { diff --git a/packages/modules/auth/integration-tests/__fixtures__/auth-identity/index.ts b/packages/modules/auth/integration-tests/__fixtures__/auth-identity/index.ts index fc632090a8..543cbd25bd 100644 --- a/packages/modules/auth/integration-tests/__fixtures__/auth-identity/index.ts +++ b/packages/modules/auth/integration-tests/__fixtures__/auth-identity/index.ts @@ -8,18 +8,15 @@ export async function createAuthIdentities( id: "test-id", entity_id: "test-id", provider: "manual", - scope: "store", }, { id: "test-id-1", entity_id: "test-id-1", provider: "manual", - scope: "store", }, { entity_id: "test-id-2", provider: "store", - scope: "store", }, ] ): Promise { diff --git a/packages/modules/auth/integration-tests/__fixtures__/providers/default-provider.ts b/packages/modules/auth/integration-tests/__fixtures__/providers/default-provider.ts new file mode 100644 index 0000000000..fc261d2ea5 --- /dev/null +++ b/packages/modules/auth/integration-tests/__fixtures__/providers/default-provider.ts @@ -0,0 +1,61 @@ +import { + AuthIdentityDTO, + AuthIdentityProviderService, + AuthenticationInput, + AuthenticationResponse, +} from "@medusajs/types" +import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils" + +export class AuthServiceFixtures extends AbstractAuthModuleProvider { + constructor() { + super( + {}, + { provider: "plaintextpass", displayName: "plaintextpass Fixture" } + ) + } + + async authenticate( + authenticationData: AuthenticationInput, + service: AuthIdentityProviderService + ): Promise { + const { email, password } = authenticationData.body ?? {} + let authIdentity: AuthIdentityDTO | undefined + try { + authIdentity = await service.retrieve({ + entity_id: email, + provider: this.provider, + }) + + if (authIdentity.provider_metadata?.password === password) { + return { + success: true, + authIdentity, + } + } + } catch (error) { + if (error.type === MedusaError.Types.NOT_FOUND) { + const createdAuthIdentity = await service.create({ + entity_id: email, + provider: this.provider, + provider_metadata: { + password, + }, + }) + + return { + success: true, + authIdentity: createdAuthIdentity, + } + } + + return { success: false, error: error.message } + } + + return { + success: false, + error: "Invalid email or password", + } + } +} + +export const services = [AuthServiceFixtures] diff --git a/packages/modules/auth/integration-tests/__fixtures__/providers/index.ts b/packages/modules/auth/integration-tests/__fixtures__/providers/index.ts new file mode 100644 index 0000000000..e19230b5b7 --- /dev/null +++ b/packages/modules/auth/integration-tests/__fixtures__/providers/index.ts @@ -0,0 +1 @@ +export * from "./default-provider" diff --git a/packages/modules/auth/integration-tests/__tests__/services/module/auth-identity.spec.ts b/packages/modules/auth/integration-tests/__tests__/auth-module-service/auth-identity.spec.ts similarity index 98% rename from packages/modules/auth/integration-tests/__tests__/services/module/auth-identity.spec.ts rename to packages/modules/auth/integration-tests/__tests__/auth-module-service/auth-identity.spec.ts index 51781b56d5..5379331336 100644 --- a/packages/modules/auth/integration-tests/__tests__/services/module/auth-identity.spec.ts +++ b/packages/modules/auth/integration-tests/__tests__/auth-module-service/auth-identity.spec.ts @@ -1,6 +1,6 @@ import { IAuthModuleService } from "@medusajs/types" import { Modules } from "@medusajs/modules-sdk" -import { createAuthIdentities } from "../../../__fixtures__/auth-identity" +import { createAuthIdentities } from "../../__fixtures__/auth-identity" import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" jest.setTimeout(30000) @@ -216,7 +216,6 @@ moduleIntegrationTestRunner({ id: "test", provider: "manual", entity_id: "test", - scope: "store", }, ]) diff --git a/packages/modules/auth/integration-tests/__tests__/auth-module-service/index.spec.ts b/packages/modules/auth/integration-tests/__tests__/auth-module-service/index.spec.ts new file mode 100644 index 0000000000..1a9ff444fe --- /dev/null +++ b/packages/modules/auth/integration-tests/__tests__/auth-module-service/index.spec.ts @@ -0,0 +1,110 @@ +import { Modules } from "@medusajs/modules-sdk" +import { IAuthModuleService } from "@medusajs/types" +import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" +import { resolve } from "path" + +let moduleOptions = { + providers: [ + { + resolve: resolve( + process.cwd() + + "/integration-tests/__fixtures__/providers/default-provider" + ), + options: { + config: { + plaintextpass: {}, + }, + }, + }, + ], +} + +jest.setTimeout(30000) + +moduleIntegrationTestRunner({ + moduleName: Modules.AUTH, + moduleOptions, + testSuite: ({ service }: SuiteOptions) => + describe("Auth Module Service", () => { + beforeEach(async () => { + await service.create({ + entity_id: "test@admin.com", + provider: "plaintextpass", + provider_metadata: { + password: "plaintext", + }, + }) + }) + + it("it fails if the provider does not exist", async () => { + const err = await service + .authenticate("facebook", { + body: { + email: "test@admin.com", + password: "password", + }, + }) + .catch((e) => e) + + expect(err).toEqual({ + success: false, + error: "Could not find a auth provider with id: facebook", + }) + }) + + it("successfully calls the provider for authentication if correct password", async () => { + const result = await service.authenticate("plaintextpass", { + body: { + email: "test@admin.com", + password: "plaintext", + }, + }) + + expect(result).toEqual( + expect.objectContaining({ + success: true, + authIdentity: expect.objectContaining({ + id: expect.any(String), + entity_id: "test@admin.com", + }), + }) + ) + }) + + it("should fail if the password is incorrect", async () => { + const result = await service + .authenticate("plaintextpass", { + body: { + email: "test@admin.com", + password: "incorrect", + }, + }) + .catch((e) => e) + + expect(result).toEqual( + expect.objectContaining({ + success: false, + error: "Invalid email or password", + }) + ) + }) + + it("successfully create a new entity if nonexistent", async () => { + const result = await service.authenticate("plaintextpass", { + body: { + email: "new@admin.com", + password: "newpass", + }, + }) + + const dbAuthIdentity = await service.retrieve(result.authIdentity.id) + + expect(dbAuthIdentity).toEqual( + expect.objectContaining({ + id: expect.any(String), + entity_id: "new@admin.com", + }) + ) + }) + }), +}) diff --git a/packages/modules/auth/integration-tests/__tests__/services/auth-identity/index.spec.ts b/packages/modules/auth/integration-tests/__tests__/services/auth-identity/index.spec.ts deleted file mode 100644 index 10727961f6..0000000000 --- a/packages/modules/auth/integration-tests/__tests__/services/auth-identity/index.spec.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { createAuthIdentities } from "../../../__fixtures__/auth-identity" -import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" -import { Modules } from "@medusajs/modules-sdk" -import { IAuthModuleService } from "@medusajs/types" - -jest.setTimeout(30000) - -moduleIntegrationTestRunner({ - moduleName: Modules.AUTH, - testSuite: ({ - MikroOrmWrapper, - service, - }: SuiteOptions) => { - describe("AuthIdentity Service", () => { - beforeEach(async () => { - await createAuthIdentities(MikroOrmWrapper.forkManager()) - }) - - describe("list", () => { - it("should list authIdentities", async () => { - const authIdentities = await service.list() - const serialized = JSON.parse(JSON.stringify(authIdentities)) - - expect(serialized).toEqual([ - expect.objectContaining({ - provider: "store", - }), - expect.objectContaining({ - provider: "manual", - }), - expect.objectContaining({ - provider: "manual", - }), - ]) - }) - - it("should list authIdentities by id", async () => { - const authIdentities = await service.list({ - id: ["test-id"], - }) - - expect(authIdentities).toEqual([ - expect.objectContaining({ - id: "test-id", - }), - ]) - }) - - it("should list authIdentities by provider_id", async () => { - const authIdentities = await service.list({ - provider: "manual", - }) - - const serialized = JSON.parse(JSON.stringify(authIdentities)) - - expect(serialized).toEqual([ - expect.objectContaining({ - id: "test-id", - }), - expect.objectContaining({ - id: "test-id-1", - }), - ]) - }) - }) - - describe("listAndCount", () => { - it("should list authIdentities", async () => { - const [authIdentities, count] = await service.listAndCount() - const serialized = JSON.parse(JSON.stringify(authIdentities)) - - expect(count).toEqual(3) - expect(serialized).toEqual([ - expect.objectContaining({ - provider: "store", - }), - expect.objectContaining({ - provider: "manual", - }), - expect.objectContaining({ - provider: "manual", - }), - ]) - }) - - it("should listAndCount authIdentities by provider_id", async () => { - const [authIdentities, count] = await service.listAndCount({ - provider: "manual", - }) - - expect(count).toEqual(2) - expect(authIdentities).toEqual([ - expect.objectContaining({ - id: "test-id", - }), - expect.objectContaining({ - id: "test-id-1", - }), - ]) - }) - }) - - describe("retrieve", () => { - const id = "test-id" - - it("should return an authIdentity for the given id", async () => { - const authIdentity = await service.retrieve(id) - - expect(authIdentity).toEqual( - expect.objectContaining({ - id, - }) - ) - }) - - it("should return authIdentity based on config select param", async () => { - const authIdentity = await service.retrieve(id, { - select: ["id"], - }) - - const serialized = JSON.parse(JSON.stringify(authIdentity)) - - expect(serialized).toEqual({ - id, - }) - }) - - it("should throw an error when an authIdentity with the given id does not exist", async () => { - let error - - try { - await service.retrieve("does-not-exist") - } catch (e) { - error = e - } - - expect(error.message).toEqual( - "AuthIdentity with id: does-not-exist was not found" - ) - }) - - it("should throw an error when a authIdentityId is not provided", async () => { - let error - - try { - await service.retrieve(undefined as unknown as string) - } catch (e) { - error = e - } - - expect(error.message).toEqual("authIdentity - id must be defined") - }) - }) - - describe("delete", () => { - it("should delete the authIdentities given an id successfully", async () => { - const id = "test-id" - - await service.delete([id]) - - const authIdentities = await service.list({ - id: [id], - }) - - expect(authIdentities).toHaveLength(0) - }) - }) - - describe("update", () => { - it("should throw an error when a id does not exist", async () => { - let error - - try { - await service.update([ - { - id: "does-not-exist", - }, - ]) - } catch (e) { - error = e - } - - expect(error.message).toEqual( - 'AuthIdentity with id "does-not-exist" not found' - ) - }) - - it("should update authIdentity", async () => { - const id = "test-id" - - await service.update([ - { - id, - provider_metadata: { email: "test@email.com" }, - }, - ]) - - const [authIdentity] = await service.list({ id: [id] }) - expect(authIdentity).toEqual( - expect.objectContaining({ - provider_metadata: { email: "test@email.com" }, - }) - ) - }) - }) - - describe("create", () => { - it("should create a authIdentity successfully", async () => { - await service.create([ - { - id: "test", - provider: "manual", - entity_id: "test", - scope: "store", - }, - ]) - - const [authIdentity] = await service.list({ - id: ["test"], - }) - - expect(authIdentity).toEqual( - expect.objectContaining({ - id: "test", - }) - ) - }) - }) - }) - }, -}) diff --git a/packages/modules/auth/integration-tests/__tests__/services/module/providers.spec.ts b/packages/modules/auth/integration-tests/__tests__/services/module/providers.spec.ts deleted file mode 100644 index a4f907719d..0000000000 --- a/packages/modules/auth/integration-tests/__tests__/services/module/providers.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Modules } from "@medusajs/modules-sdk" - -import { IAuthModuleService } from "@medusajs/types" -import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" - -jest.setTimeout(30000) - -moduleIntegrationTestRunner({ - moduleName: Modules.AUTH, - testSuite: ({ - MikroOrmWrapper, - service, - }: SuiteOptions) => { - describe("AuthModuleService - AuthProvider", () => { - describe("authenticate", () => { - it("authenticate validates that a provider is registered in container", async () => { - const { success, error } = await service.authenticate( - "notRegistered", - {} as any - ) - - expect(success).toBe(false) - expect(error).toEqual( - "AuthenticationProvider: notRegistered wasn't registered in the module. Have you configured your options correctly?" - ) - }) - - it("fails to authenticate using a valid provider with an invalid scope", async () => { - const { success, error } = await service.authenticate("emailpass", { - authScope: "non-existing", - } as any) - - expect(success).toBe(false) - expect(error).toEqual( - `Scope "non-existing" is not valid for provider emailpass` - ) - }) - }) - }) - }, -}) diff --git a/packages/modules/auth/integration-tests/__tests__/services/providers/username-password.spec.ts b/packages/modules/auth/integration-tests/__tests__/services/providers/username-password.spec.ts deleted file mode 100644 index 0398055b98..0000000000 --- a/packages/modules/auth/integration-tests/__tests__/services/providers/username-password.spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { MedusaModule, Modules } from "@medusajs/modules-sdk" - -import { IAuthModuleService } from "@medusajs/types" -import Scrypt from "scrypt-kdf" -import { createAuthIdentities } from "../../../__fixtures__/auth-identity" -import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" - -jest.setTimeout(30000) -const seedDefaultData = async (manager) => { - await createAuthIdentities(manager) -} - -moduleIntegrationTestRunner({ - moduleName: Modules.AUTH, - moduleOptions: { - providers: [ - { - name: "emailpass", - scopes: { - admin: {}, - store: {}, - }, - }, - ], - }, - testSuite: ({ - MikroOrmWrapper, - service, - }: SuiteOptions) => { - describe("AuthModuleService - AuthProvider", () => { - describe("authenticate", () => { - it("authenticate validates that a provider is registered in container", async () => { - const password = "supersecret" - const email = "test@test.com" - const passwordHash = ( - await Scrypt.kdf(password, { logN: 15, r: 8, p: 1 }) - ).toString("base64") - - await seedDefaultData(MikroOrmWrapper.forkManager()) - await createAuthIdentities(MikroOrmWrapper.forkManager(), [ - // Add authenticated user - { - provider: "emailpass", - entity_id: email, - scope: "store", - provider_metadata: { - password: passwordHash, - }, - }, - ]) - - const res = await service.authenticate("emailpass", { - body: { - email: "test@test.com", - password: password, - }, - authScope: "store", - } as any) - - expect(res).toEqual({ - success: true, - authIdentity: expect.objectContaining({ - entity_id: email, - provider_metadata: {}, - }), - }) - }) - - it("fails when no password is given", async () => { - await seedDefaultData(MikroOrmWrapper.forkManager()) - - const res = await service.authenticate("emailpass", { - body: { email: "test@test.com" }, - authScope: "store", - } as any) - - expect(res).toEqual({ - success: false, - error: "Password should be a string", - }) - }) - - it("fails when no email is given", async () => { - await seedDefaultData(MikroOrmWrapper.forkManager()) - - const res = await service.authenticate("emailpass", { - body: { password: "supersecret" }, - authScope: "store", - } as any) - - expect(res).toEqual({ - success: false, - error: "Email should be a string", - }) - }) - - it("fails with an invalid password", async () => { - const password = "supersecret" - const email = "test@test.com" - const passwordHash = ( - await Scrypt.kdf(password, { logN: 15, r: 8, p: 1 }) - ).toString("base64") - - await seedDefaultData(MikroOrmWrapper.forkManager()) - await createAuthIdentities(MikroOrmWrapper.forkManager(), [ - // Add authenticated user - { - provider: "emailpass", - scope: "store", - entity_id: email, - provider_metadata: { - password_hash: passwordHash, - }, - }, - ]) - - const res = await service.authenticate("emailpass", { - body: { - email: "test@test.com", - password: "password", - }, - authScope: "store", - } as any) - - expect(res).toEqual({ - success: false, - error: "Invalid email or password", - }) - }) - }) - }) - }, -}) diff --git a/packages/modules/auth/package.json b/packages/modules/auth/package.json index c3be5029f4..ad127b819f 100644 --- a/packages/modules/auth/package.json +++ b/packages/modules/auth/package.json @@ -48,6 +48,7 @@ "typescript": "^5.1.6" }, "dependencies": { + "@medusajs/auth-emailpass": "0.0.1", "@medusajs/modules-sdk": "^1.12.9", "@medusajs/types": "^1.11.14", "@medusajs/utils": "^1.11.7", diff --git a/packages/modules/auth/src/loaders/providers.ts b/packages/modules/auth/src/loaders/providers.ts index af8772efe9..28cbdde3d2 100644 --- a/packages/modules/auth/src/loaders/providers.ts +++ b/packages/modules/auth/src/loaders/providers.ts @@ -1,21 +1,26 @@ -import * as defaultProviders from "@providers" +import EmailPassProvider from "@medusajs/auth-emailpass" +import { LoaderOptions, ModulesSdkTypes, ModuleProvider } from "@medusajs/types" +import { Lifetime, asFunction, asValue } from "awilix" +import { moduleProviderLoader } from "@medusajs/modules-sdk" import { - AuthModuleProviderConfig, - AuthProviderScope, - LoaderOptions, - ModulesSdkTypes, -} from "@medusajs/types" -import { - AwilixContainer, - ClassOrFunctionReturning, - Constructor, - Resolver, - asClass, -} from "awilix" + AuthIdentifiersRegistrationName, + AuthProviderRegistrationPrefix, +} from "@types" -type AuthModuleProviders = { - providers: AuthModuleProviderConfig[] +const registrationFn = async (klass, container, pluginOptions) => { + Object.entries(pluginOptions.config || []).map(([name, config]) => { + container.register({ + [AuthProviderRegistrationPrefix + name]: asFunction( + (cradle) => new klass(cradle, config), + { + lifetime: klass.LIFE_TIME || Lifetime.SINGLETON, + } + ), + }) + + container.registerAdd(AuthIdentifiersRegistrationName, asValue(name)) + }) } export default async ({ @@ -25,48 +30,28 @@ export default async ({ ( | ModulesSdkTypes.ModuleServiceInitializeOptions | ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions - ) & - AuthModuleProviders + ) & { providers: ModuleProvider[] } >): Promise => { - const providerMap = new Map( - options?.providers?.map((provider) => [provider.name, provider.scopes]) ?? - [] - ) + // TODO: Temporary settings used by the starter, remove once the auth module is updated + const isLegacyOptions = + options?.providers?.length && !!(options?.providers[0] as any)?.name - // if(options?.providers?.length) { - // TODO: implement plugin provider registration - // } + // Note: For now we want to inject some providers out of the box + const providerConfig = [ + { + resolve: EmailPassProvider, + options: { + config: { + emailpass: {}, + }, + }, + }, + ...(isLegacyOptions ? [] : options?.providers ?? []), + ] - const providersToLoad = Object.values(defaultProviders) - - for (const provider of providersToLoad) { - container.register({ - [`auth_provider_${provider.PROVIDER}`]: asClass( - provider as Constructor - ) - .singleton() - .inject(() => ({ scopes: providerMap.get(provider.PROVIDER) ?? {} })), - }) - } - - container.register({ - [`auth_providers`]: asArray(providersToLoad, providerMap), + await moduleProviderLoader({ + container, + providers: providerConfig, + registerServiceFn: registrationFn, }) } - -function asArray( - resolvers: (ClassOrFunctionReturning | Resolver)[], - providerScopeMap: Map> -): { resolve: (container: AwilixContainer) => unknown[] } { - return { - resolve: (container: AwilixContainer) => - resolvers.map((resolver) => - asClass(resolver as Constructor) - .inject(() => ({ - // @ts-ignore - scopes: providerScopeMap.get(resolver.PROVIDER) ?? {}, - })) - .resolve(container) - ), - } -} diff --git a/packages/modules/auth/src/migrations/.snapshot-medusa-auth.json b/packages/modules/auth/src/migrations/.snapshot-medusa-auth.json index 6aebf22d6c..a8e15bdf4b 100644 --- a/packages/modules/auth/src/migrations/.snapshot-medusa-auth.json +++ b/packages/modules/auth/src/migrations/.snapshot-medusa-auth.json @@ -31,15 +31,6 @@ "nullable": false, "mappedType": "text" }, - "scope": { - "name": "scope", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, "user_metadata": { "name": "user_metadata", "type": "jsonb", @@ -72,8 +63,8 @@ "schema": "public", "indexes": [ { - "keyName": "IDX_auth_identity_provider_scope_entity_id", - "columnNames": ["provider", "scope", "entity_id"], + "keyName": "IDX_auth_identity_provider_entity_id", + "columnNames": ["provider", "entity_id"], "composite": true, "primary": false, "unique": true diff --git a/packages/modules/auth/src/migrations/Migration20240205025924.ts b/packages/modules/auth/src/migrations/Migration20240205025924.ts deleted file mode 100644 index 8e8e16a973..0000000000 --- a/packages/modules/auth/src/migrations/Migration20240205025924.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Migration } from "@mikro-orm/migrations" - -export class Migration20240205025924 extends Migration { - async up(): Promise { - this.addSql( - 'create table if not exists "auth_identity" ("id" text not null, "entity_id" text not null, "provider" text not null, "scope" text not null, "user_metadata" jsonb null, "app_metadata" jsonb not null, "provider_metadata" jsonb null, constraint "auth_identity_pkey" primary key ("id"));' - ) - this.addSql( - 'alter table "auth_identity" add constraint "IDX_auth_identity_provider_scope_entity_id" unique ("provider", "scope", "entity_id");' - ) - } - - async down(): Promise { - this.addSql('drop table if exists "auth_identity" cascade;') - } -} diff --git a/packages/modules/auth/src/migrations/Migration20240205025928.ts b/packages/modules/auth/src/migrations/Migration20240205025928.ts new file mode 100644 index 0000000000..1590f823ba --- /dev/null +++ b/packages/modules/auth/src/migrations/Migration20240205025928.ts @@ -0,0 +1,21 @@ +import { Migration } from "@mikro-orm/migrations" + +export class Migration20240205025928 extends Migration { + async up(): Promise { + this.addSql( + 'create table if not exists "auth_identity" ("id" text not null, "entity_id" text not null, "provider" text not null, "user_metadata" jsonb null, "app_metadata" jsonb null, "provider_metadata" jsonb null, constraint "auth_identity_pkey" primary key ("id"));' + ) + this.addSql( + 'alter table "auth_identity" add constraint "IDX_auth_identity_provider_entity_id" unique ("provider", "entity_id");' + ) + + this.addSql('alter table "auth_identity" drop column if exists "scope";') + this.addSql( + `alter table "auth_identity" alter column "app_metadata" drop not null;` + ) + } + + async down(): Promise { + this.addSql('drop table if exists "auth_identity" cascade;') + } +} diff --git a/packages/modules/auth/src/models/auth-identity.ts b/packages/modules/auth/src/models/auth-identity.ts index 9a938a503f..75d3f805fe 100644 --- a/packages/modules/auth/src/models/auth-identity.ts +++ b/packages/modules/auth/src/models/auth-identity.ts @@ -14,8 +14,8 @@ type OptionalFields = "provider_metadata" | "app_metadata" | "user_metadata" @Entity() @Unique({ - properties: ["provider", "scope", "entity_id"], - name: "IDX_auth_identity_provider_scope_entity_id", + properties: ["provider", "entity_id"], + name: "IDX_auth_identity_provider_entity_id", }) export default class AuthIdentity { [OptionalProps]: OptionalFields @@ -29,14 +29,11 @@ export default class AuthIdentity { @Property({ columnType: "text" }) provider: string - @Property({ columnType: "text" }) - scope: string - @Property({ columnType: "jsonb", nullable: true }) user_metadata: Record | null - @Property({ columnType: "jsonb" }) - app_metadata: Record = {} + @Property({ columnType: "jsonb", nullable: true }) + app_metadata: Record | null @Property({ columnType: "jsonb", nullable: true }) provider_metadata: Record | null = null diff --git a/packages/modules/auth/src/module-definition.ts b/packages/modules/auth/src/module-definition.ts index 18b27ccc31..dbf1db9d85 100644 --- a/packages/modules/auth/src/module-definition.ts +++ b/packages/modules/auth/src/module-definition.ts @@ -1,5 +1,5 @@ -import { AuthModuleService } from "@services" import { ModuleExports } from "@medusajs/types" +import { AuthModuleService } from "@services" import loadProviders from "./loaders/providers" const service = AuthModuleService diff --git a/packages/modules/auth/src/providers/email-password.ts b/packages/modules/auth/src/providers/email-password.ts deleted file mode 100644 index 79715c4f24..0000000000 --- a/packages/modules/auth/src/providers/email-password.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { AuthenticationInput, AuthenticationResponse } from "@medusajs/types" -import { - AbstractAuthModuleProvider, - MedusaError, - isString, -} from "@medusajs/utils" - -import { AuthIdentityService } from "@services" -import Scrypt from "scrypt-kdf" - -class EmailPasswordProvider extends AbstractAuthModuleProvider { - public static PROVIDER = "emailpass" - public static DISPLAY_NAME = "Email/Password Authentication" - - protected readonly authIdentitySerivce_: AuthIdentityService - - constructor({ - authIdentityService, - }: { - authIdentityService: AuthIdentityService - }) { - super(arguments[0], { - provider: EmailPasswordProvider.PROVIDER, - displayName: EmailPasswordProvider.DISPLAY_NAME, - }) - - this.authIdentitySerivce_ = authIdentityService - } - - private getHashConfig() { - const scopeConfig = this.scopeConfig_.hashConfig as - | Scrypt.ScryptParams - | undefined - - const defaultHashConfig = { logN: 15, r: 8, p: 1 } - - // Return custom defined hash config or default hash parameters - return scopeConfig ?? defaultHashConfig - } - - async authenticate( - userData: AuthenticationInput - ): Promise { - const { email, password } = userData.body - - if (!password || !isString(password)) { - return { - success: false, - error: "Password should be a string", - } - } - - if (!email || !isString(email)) { - return { - success: false, - error: "Email should be a string", - } - } - let authIdentity - - try { - authIdentity = - await this.authIdentitySerivce_.retrieveByProviderAndEntityId( - email, - EmailPasswordProvider.PROVIDER - ) - } catch (error) { - if (error.type === MedusaError.Types.NOT_FOUND) { - const password_hash = await Scrypt.kdf(password, this.getHashConfig()) - - const [createdAuthIdentity] = await this.authIdentitySerivce_.create([ - { - entity_id: email, - provider: EmailPasswordProvider.PROVIDER, - scope: this.scope_, - provider_metadata: { - password: password_hash.toString("base64"), - }, - }, - ]) - - return { - success: true, - authIdentity: JSON.parse(JSON.stringify(createdAuthIdentity)), - } - } - return { success: false, error: error.message } - } - - const password_hash = authIdentity.provider_metadata?.password - - if (isString(password_hash)) { - const buf = Buffer.from(password_hash as string, "base64") - - const success = await Scrypt.verify(buf, password) - - if (success) { - delete authIdentity.provider_metadata!.password - - return { - success, - authIdentity: JSON.parse(JSON.stringify(authIdentity)), - } - } - } - - return { - success: false, - error: "Invalid email or password", - } - } -} - -export default EmailPasswordProvider diff --git a/packages/modules/auth/src/providers/google.ts b/packages/modules/auth/src/providers/google.ts index 15680f82d2..ad1f62a3d0 100644 --- a/packages/modules/auth/src/providers/google.ts +++ b/packages/modules/auth/src/providers/google.ts @@ -1,13 +1,12 @@ import { AuthenticationInput, AuthenticationResponse } from "@medusajs/types" import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils" -import { AuthIdentityService } from "@services" import jwt, { JwtPayload } from "jsonwebtoken" import { AuthorizationCode } from "simple-oauth2" import url from "url" type InjectedDependencies = { - authIdentityService: AuthIdentityService + authIdentityService: any } type ProviderConfig = { @@ -18,15 +17,12 @@ type ProviderConfig = { } class GoogleProvider extends AbstractAuthModuleProvider { - public static PROVIDER = "google" - public static DISPLAY_NAME = "Google Authentication" + protected readonly authIdentityService_: any - protected readonly authIdentityService_: AuthIdentityService - - constructor({ authIdentityService }: InjectedDependencies) { + constructor({ authIdentityService }: InjectedDependencies, options: any) { super(arguments[0], { - provider: GoogleProvider.PROVIDER, - displayName: GoogleProvider.DISPLAY_NAME, + provider: "google", + displayName: "Google Authentication", }) this.authIdentityService_ = authIdentityService @@ -72,6 +68,9 @@ class GoogleProvider extends AbstractAuthModuleProvider { } const code = req.query?.code ?? req.body?.code + if (!code) { + return { success: false, error: "No code provided" } + } return await this.validateCallbackToken(code, config) } @@ -89,16 +88,15 @@ class GoogleProvider extends AbstractAuthModuleProvider { authIdentity = await this.authIdentityService_.retrieveByProviderAndEntityId( entity_id, - GoogleProvider.PROVIDER + this.provider ) } catch (error) { if (error.type === MedusaError.Types.NOT_FOUND) { const [createdAuthIdentity] = await this.authIdentityService_.create([ { entity_id, - provider: GoogleProvider.PROVIDER, + provider: this.provider, user_metadata: jwtData!.payload, - scope: this.scope_, }, ]) authIdentity = createdAuthIdentity @@ -132,7 +130,7 @@ class GoogleProvider extends AbstractAuthModuleProvider { accessToken.token.id_token ) - const { successRedirectUrl } = this.getConfigFromScope() + const { successRedirectUrl } = this.getConfig() return { success, @@ -144,8 +142,10 @@ class GoogleProvider extends AbstractAuthModuleProvider { } } - private getConfigFromScope(): ProviderConfig { - const config: Partial = { ...this.scopeConfig_ } + private getConfig(): ProviderConfig { + // TODO: Fetch this from provider config + // const config: Partial = { ...this.scopeConfig_ } + const config = {} as any if (!config.clientID) { throw new Error("Google clientID is required") @@ -163,7 +163,7 @@ class GoogleProvider extends AbstractAuthModuleProvider { } private originalURL(req: AuthenticationInput) { - const host = req.headers.host + const host = req.headers?.host const protocol = req.protocol const path = req.url || "" @@ -173,7 +173,7 @@ class GoogleProvider extends AbstractAuthModuleProvider { private async getProviderConfig( req: AuthenticationInput ): Promise { - const config = this.getConfigFromScope() + const config = this.getConfig() const callbackURL = config.callbackURL diff --git a/packages/modules/auth/src/providers/index.ts b/packages/modules/auth/src/providers/index.ts index 7f7c8e594b..14dabd356d 100644 --- a/packages/modules/auth/src/providers/index.ts +++ b/packages/modules/auth/src/providers/index.ts @@ -1,2 +1 @@ -export { default as EmailPasswordProvider } from "./email-password" -export { default as GoogleProvider } from "./google" \ No newline at end of file +export { default as GoogleProvider } from "./google" diff --git a/packages/modules/auth/src/services/auth-identity.ts b/packages/modules/auth/src/services/auth-identity.ts deleted file mode 100644 index 80a11b8446..0000000000 --- a/packages/modules/auth/src/services/auth-identity.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - AuthTypes, - Context, - DAL, - FindConfig, - RepositoryService, -} from "@medusajs/types" -import { - InjectManager, - MedusaContext, - MedusaError, - ModulesSdkUtils, -} from "@medusajs/utils" -import { AuthIdentity } from "@models" - -type InjectedDependencies = { - baseRepository: DAL.RepositoryService - authIdentityRepository: DAL.RepositoryService -} - -export default class AuthIdentityService< - TEntity extends AuthIdentity = AuthIdentity -> extends ModulesSdkUtils.internalModuleServiceFactory( - AuthIdentity -) { - protected readonly authIdentityRepository_: RepositoryService - protected baseRepository_: DAL.RepositoryService - - constructor(container: InjectedDependencies) { - // @ts-ignore - super(...arguments) - this.authIdentityRepository_ = container.authIdentityRepository - this.baseRepository_ = container.baseRepository - } - - @InjectManager("authIdentityRepository_") - async retrieveByProviderAndEntityId< - TEntityMethod = AuthTypes.AuthIdentityDTO - >( - entityId: string, - provider: string, - config: FindConfig = {}, - @MedusaContext() sharedContext: Context = {} - ): Promise { - const queryConfig = ModulesSdkUtils.buildQuery( - { entity_id: entityId, provider }, - { ...config, take: 1 } - ) - const [result] = await this.authIdentityRepository_.find( - queryConfig, - sharedContext - ) - - if (!result) { - throw new MedusaError( - MedusaError.Types.NOT_FOUND, - `AuthIdentity with entity_id: "${entityId}" and provider: "${provider}" not found` - ) - } - - return await this.baseRepository_.serialize( - result - ) - } -} diff --git a/packages/modules/auth/src/services/auth-module.ts b/packages/modules/auth/src/services/auth-module.ts index 421b4df7b7..c94264a660 100644 --- a/packages/modules/auth/src/services/auth-module.ts +++ b/packages/modules/auth/src/services/auth-module.ts @@ -1,6 +1,7 @@ import { AuthenticationInput, AuthenticationResponse, + AuthIdentityProviderService, AuthTypes, Context, DAL, @@ -14,16 +15,17 @@ import { AuthIdentity } from "@models" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" import { - AbstractAuthModuleProvider, InjectManager, MedusaContext, MedusaError, ModulesSdkUtils, } from "@medusajs/utils" +import AuthProviderService from "./auth-provider" type InjectedDependencies = { baseRepository: DAL.RepositoryService authIdentityService: ModulesSdkTypes.InternalModuleService + authProviderService: AuthProviderService } const generateMethodForModels = [AuthIdentity] @@ -42,9 +44,14 @@ export default class AuthModuleService< { protected baseRepository_: DAL.RepositoryService protected authIdentityService_: ModulesSdkTypes.InternalModuleService + protected readonly authProviderService_: AuthProviderService constructor( - { authIdentityService, baseRepository }: InjectedDependencies, + { + authIdentityService, + authProviderService, + baseRepository, + }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { // @ts-ignore @@ -52,6 +59,7 @@ export default class AuthModuleService< this.baseRepository_ = baseRepository this.authIdentityService_ = authIdentityService + this.authProviderService_ = authProviderService } __joinerConfig(): ModuleJoinerConfig { @@ -116,34 +124,16 @@ export default class AuthModuleService< return Array.isArray(data) ? serializedUsers : serializedUsers[0] } - protected getRegisteredAuthenticationProvider( - provider: string, - { authScope }: AuthenticationInput - ): AbstractAuthModuleProvider { - let containerProvider: AbstractAuthModuleProvider - try { - containerProvider = this.__container__[`auth_provider_${provider}`] - } catch (error) { - throw new MedusaError( - MedusaError.Types.NOT_FOUND, - `AuthenticationProvider: ${provider} wasn't registered in the module. Have you configured your options correctly?` - ) - } - - return containerProvider.withScope(authScope) - } - async authenticate( provider: string, authenticationData: AuthenticationInput ): Promise { try { - const registeredProvider = this.getRegisteredAuthenticationProvider( + return await this.authProviderService_.authenticate( provider, - authenticationData + authenticationData, + this.getAuthIdentityProviderService() ) - - return await registeredProvider.authenticate(authenticationData) } catch (error) { return { success: false, error: error.message } } @@ -154,14 +144,49 @@ export default class AuthModuleService< authenticationData: AuthenticationInput ): Promise { try { - const registeredProvider = this.getRegisteredAuthenticationProvider( + return await this.authProviderService_.validateCallback( provider, - authenticationData + authenticationData, + this.getAuthIdentityProviderService() ) - - return await registeredProvider.validateCallback(authenticationData) } catch (error) { return { success: false, error: error.message } } } + + getAuthIdentityProviderService(): AuthIdentityProviderService { + return { + retrieve: async ({ entity_id, provider }) => { + const authIdentities = await this.authIdentityService_.list({ + entity_id, + provider, + }) + + 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}"` + ) + } + + return await this.baseRepository_.serialize( + authIdentities[0] + ) + }, + create: async (data: AuthTypes.CreateAuthIdentityDTO) => { + const createdAuthIdentity = await this.authIdentityService_.create(data) + + return await this.baseRepository_.serialize( + createdAuthIdentity + ) + }, + } + } } diff --git a/packages/modules/auth/src/services/auth-provider.ts b/packages/modules/auth/src/services/auth-provider.ts new file mode 100644 index 0000000000..eb33eca60c --- /dev/null +++ b/packages/modules/auth/src/services/auth-provider.ts @@ -0,0 +1,56 @@ +import { + AuthTypes, + AuthenticationInput, + AuthIdentityProviderService, + AuthenticationResponse, +} from "@medusajs/types" +import { MedusaError } from "medusa-core-utils" +import { AuthProviderRegistrationPrefix } from "@types" + +type InjectedDependencies = { + [ + key: `${typeof AuthProviderRegistrationPrefix}${string}` + ]: AuthTypes.IAuthProvider +} + +export default class AuthProviderService { + protected dependencies: InjectedDependencies + + constructor(container: InjectedDependencies) { + this.dependencies = container + } + + protected retrieveProviderRegistration( + providerId: string + ): AuthTypes.IAuthProvider { + try { + return this.dependencies[`${AuthProviderRegistrationPrefix}${providerId}`] + } catch (err) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Could not find a auth provider with id: ${providerId}` + ) + } + } + + async authenticate( + provider: string, + auth: AuthenticationInput, + authIdentityProviderService: AuthIdentityProviderService + ): Promise { + const providerHandler = this.retrieveProviderRegistration(provider) + return await providerHandler.authenticate(auth, authIdentityProviderService) + } + + async validateCallback( + provider: string, + auth: AuthenticationInput, + authIdentityProviderService: AuthIdentityProviderService + ): Promise { + const providerHandler = this.retrieveProviderRegistration(provider) + return await providerHandler.validateCallback( + auth, + authIdentityProviderService + ) + } +} diff --git a/packages/modules/auth/src/services/index.ts b/packages/modules/auth/src/services/index.ts index 985249c436..2f650fe761 100644 --- a/packages/modules/auth/src/services/index.ts +++ b/packages/modules/auth/src/services/index.ts @@ -1,2 +1,2 @@ export { default as AuthModuleService } from "./auth-module" -export { default as AuthIdentityService } from "./auth-identity" +export { default as AuthProviderService } from "./auth-provider" diff --git a/packages/modules/auth/src/types/index.ts b/packages/modules/auth/src/types/index.ts index 0f252977b0..4481d309c2 100644 --- a/packages/modules/auth/src/types/index.ts +++ b/packages/modules/auth/src/types/index.ts @@ -1,5 +1,29 @@ +import { ModuleProviderExports } from "@medusajs/types" +import { ModuleServiceInitializeOptions } from "@medusajs/types" import { Logger } from "@medusajs/types" export type InitializeModuleInjectableDependencies = { logger?: Logger } + +export const AuthIdentifiersRegistrationName = "auth_providers_identifier" + +export const AuthProviderRegistrationPrefix = "au_" + +export type AuthModuleOptions = Partial & { + /** + * Providers to be registered + */ + providers?: { + /** + * The module provider to be registered + */ + resolve: string | ModuleProviderExports + options: { + /** + * key value pair of the provider name and the configuration to be passed to the provider constructor + */ + config: Record + } + }[] +} diff --git a/packages/modules/providers/auth-emailpass/.gitignore b/packages/modules/providers/auth-emailpass/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/providers/auth-emailpass/README.md b/packages/modules/providers/auth-emailpass/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/providers/auth-emailpass/integration-tests/__tests__/services.spec.ts b/packages/modules/providers/auth-emailpass/integration-tests/__tests__/services.spec.ts new file mode 100644 index 0000000000..131d8c43b3 --- /dev/null +++ b/packages/modules/providers/auth-emailpass/integration-tests/__tests__/services.spec.ts @@ -0,0 +1,138 @@ +import { MedusaError } from "@medusajs/utils" +import Scrypt from "scrypt-kdf" +import { EmailPassAuthService } from "../../src/services/emailpass" +jest.setTimeout(100000) + +describe("Email password auth provider", () => { + let emailpassService: EmailPassAuthService + + beforeAll(() => { + emailpassService = new EmailPassAuthService( + { + logger: console as any, + }, + {} + ) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it("return error if email is not passed", async () => { + const resp = await emailpassService.authenticate( + { body: { password: "otherpass" } }, + {} + ) + + expect(resp).toEqual({ + error: "Email should be a string", + success: false, + }) + }) + + it("return error if password is not passed", async () => { + const resp = await emailpassService.authenticate( + { body: { email: "test@admin.com" } }, + {} + ) + + expect(resp).toEqual({ + error: "Password should be a string", + success: false, + }) + }) + + it("return error if the passwords don't match", async () => { + const config = { logN: 15, r: 8, p: 1 } + const passwordHash = await Scrypt.kdf("somepass", config) + + const authServiceSpies = { + retrieve: jest.fn().mockImplementation(() => { + return { + entity_id: "test@admin.com", + provider: "emailpass", + provider_metadata: { + password: passwordHash.toString("base64"), + }, + } + }), + } + + const resp = await emailpassService.authenticate( + { body: { email: "test@admin.com", password: "otherpass" } }, + authServiceSpies + ) + + expect(authServiceSpies.retrieve).toHaveBeenCalled() + expect(resp).toEqual({ + error: "Invalid email or password", + success: false, + }) + }) + + it("return an existing entity if the passwords match", async () => { + const config = { logN: 15, r: 8, p: 1 } + const passwordHash = await Scrypt.kdf("somepass", config) + + const authServiceSpies = { + retrieve: jest.fn().mockImplementation(() => { + return { + entity_id: "test@admin.com", + provider: "emailpass", + provider_metadata: { + password: passwordHash.toString("base64"), + }, + } + }), + } + + const resp = await emailpassService.authenticate( + { body: { email: "test@admin.com", password: "somepass" } }, + authServiceSpies + ) + + expect(authServiceSpies.retrieve).toHaveBeenCalled() + expect(resp).toEqual( + expect.objectContaining({ + success: true, + authIdentity: expect.objectContaining({ + entity_id: "test@admin.com", + provider_metadata: {}, + }), + }) + ) + }) + + it("creates a new auth identity if it doesn't exist", async () => { + const authServiceSpies = { + retrieve: jest.fn().mockImplementation(() => { + throw new MedusaError(MedusaError.Types.NOT_FOUND, "Not found") + }), + create: jest.fn().mockImplementation(() => { + return { + entity_id: "test@admin.com", + provider: "emailpass", + provider_metadata: { + password: "somehash", + }, + } + }), + } + + const resp = await emailpassService.authenticate( + { body: { email: "test@admin.com", password: "test" } }, + authServiceSpies + ) + + expect(authServiceSpies.retrieve).toHaveBeenCalled() + expect(authServiceSpies.create).toHaveBeenCalled() + + expect(resp.authIdentity).toEqual( + expect.objectContaining({ + entity_id: "test@admin.com", + provider_metadata: {}, + }) + ) + }) +}) diff --git a/packages/modules/providers/auth-emailpass/jest.config.js b/packages/modules/providers/auth-emailpass/jest.config.js new file mode 100644 index 0000000000..9cf8a99080 --- /dev/null +++ b/packages/modules/providers/auth-emailpass/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + transform: { + "^.+\\.[jt]s?$": "@swc/jest", + }, + testEnvironment: `node`, + moduleFileExtensions: [`js`, `jsx`, `ts`, `tsx`, `json`], +} diff --git a/packages/modules/providers/auth-emailpass/package.json b/packages/modules/providers/auth-emailpass/package.json new file mode 100644 index 0000000000..6a4cf8287f --- /dev/null +++ b/packages/modules/providers/auth-emailpass/package.json @@ -0,0 +1,40 @@ +{ + "name": "@medusajs/auth-emailpass", + "version": "0.0.1", + "description": "Email and password credential authentication provider for Medusa", + "main": "dist/index.js", + "repository": { + "type": "git", + "url": "https://github.com/medusajs/medusa", + "directory": "packages/modules/providers/auth-emailpass" + }, + "files": [ + "dist" + ], + "engines": { + "node": ">=16" + }, + "author": "Medusa", + "license": "MIT", + "scripts": { + "prepublishOnly": "cross-env NODE_ENV=production tsc --build", + "test": "jest --passWithNoTests src", + "test:integration": "jest --forceExit -- integration-tests/**/__tests__/**/*.spec.ts", + "build": "rimraf dist && tsc -p ./tsconfig.json", + "watch": "tsc --watch" + }, + "devDependencies": { + "cross-env": "^5.2.1", + "jest": "^25.5.4", + "rimraf": "^5.0.1", + "typescript": "^4.9.5" + }, + "dependencies": { + "@medusajs/utils": "^1.11.7", + "scrypt-kdf": "^2.0.1" + }, + "keywords": [ + "medusa-provider", + "medusa-provider-auth-userpass" + ] +} diff --git a/packages/modules/providers/auth-emailpass/src/index.ts b/packages/modules/providers/auth-emailpass/src/index.ts new file mode 100644 index 0000000000..efc1ad4343 --- /dev/null +++ b/packages/modules/providers/auth-emailpass/src/index.ts @@ -0,0 +1,10 @@ +import { ModuleProviderExports } from "@medusajs/types" +import { EmailPassAuthService } from "./services/emailpass" + +const services = [EmailPassAuthService] + +const providerExport: ModuleProviderExports = { + services, +} + +export default providerExport diff --git a/packages/modules/providers/auth-emailpass/src/services/emailpass.ts b/packages/modules/providers/auth-emailpass/src/services/emailpass.ts new file mode 100644 index 0000000000..b79f0579c5 --- /dev/null +++ b/packages/modules/providers/auth-emailpass/src/services/emailpass.ts @@ -0,0 +1,110 @@ +import { + Logger, + EmailPassAuthProviderOptions, + AuthenticationResponse, + AuthenticationInput, + AuthIdentityProviderService, +} from "@medusajs/types" +import { + AbstractAuthModuleProvider, + MedusaError, + isString, +} from "@medusajs/utils" +import Scrypt from "scrypt-kdf" + +type InjectedDependencies = { + logger: Logger +} + +interface LocalServiceConfig extends EmailPassAuthProviderOptions {} + +export class EmailPassAuthService extends AbstractAuthModuleProvider { + protected config_: LocalServiceConfig + protected logger_: Logger + + constructor( + { logger }: InjectedDependencies, + options: EmailPassAuthProviderOptions + ) { + super( + {}, + { provider: "emailpass", displayName: "Email/Password Authentication" } + ) + this.config_ = options + this.logger_ = logger + } + + async authenticate( + userData: AuthenticationInput, + authIdentityService: AuthIdentityProviderService + ): Promise { + const { email, password } = userData.body ?? {} + + if (!password || !isString(password)) { + return { + success: false, + error: "Password should be a string", + } + } + + if (!email || !isString(email)) { + return { + success: false, + error: "Email should be a string", + } + } + let authIdentity + + try { + authIdentity = await authIdentityService.retrieve({ + entity_id: email, + provider: this.provider, + }) + } catch (error) { + if (error.type === MedusaError.Types.NOT_FOUND) { + const config = this.config_.hashConfig ?? { logN: 15, r: 8, p: 1 } + const passwordHash = await Scrypt.kdf(password, config) + + const createdAuthIdentity = await authIdentityService.create({ + entity_id: email, + provider: this.provider, + provider_metadata: { + password: passwordHash.toString("base64"), + }, + }) + + const copy = JSON.parse(JSON.stringify(createdAuthIdentity)) + delete copy.provider_metadata?.password + + return { + success: true, + authIdentity: copy, + } + } + + return { success: false, error: error.message } + } + + const passwordHash = authIdentity.provider_metadata?.password + + if (isString(passwordHash)) { + const buf = Buffer.from(passwordHash as string, "base64") + const success = await Scrypt.verify(buf, password) + + if (success) { + const copy = JSON.parse(JSON.stringify(authIdentity)) + delete copy.provider_metadata!.password + + return { + success, + authIdentity: copy, + } + } + } + + return { + success: false, + error: "Invalid email or password", + } + } +} diff --git a/packages/modules/providers/auth-emailpass/tsconfig.json b/packages/modules/providers/auth-emailpass/tsconfig.json new file mode 100644 index 0000000000..29ba2bf8ef --- /dev/null +++ b/packages/modules/providers/auth-emailpass/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "lib": ["es2021"], + "target": "es2021", + "jsx": "react-jsx" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, + "outDir": "./dist", + "esModuleInterop": true, + "declaration": true, + "module": "commonjs", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "noImplicitReturns": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noImplicitThis": true, + "allowJs": true, + "skipLibCheck": true, + "downlevelIteration": true, // to use ES5 specific tooling + "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/yarn.lock b/yarn.lock index 9fc6f82660..3c24ca9a8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5092,10 +5092,24 @@ __metadata: languageName: unknown linkType: soft +"@medusajs/auth-emailpass@0.0.1, @medusajs/auth-emailpass@workspace:packages/modules/providers/auth-emailpass": + version: 0.0.0-use.local + resolution: "@medusajs/auth-emailpass@workspace:packages/modules/providers/auth-emailpass" + dependencies: + "@medusajs/utils": ^1.11.7 + cross-env: ^5.2.1 + jest: ^25.5.4 + rimraf: ^5.0.1 + scrypt-kdf: ^2.0.1 + typescript: ^4.9.5 + languageName: unknown + linkType: soft + "@medusajs/auth@workspace:*, @medusajs/auth@workspace:packages/modules/auth": version: 0.0.0-use.local resolution: "@medusajs/auth@workspace:packages/modules/auth" dependencies: + "@medusajs/auth-emailpass": 0.0.1 "@medusajs/modules-sdk": ^1.12.9 "@medusajs/types": ^1.11.14 "@medusajs/utils": ^1.11.7