diff --git a/integration-tests/plugins/__tests__/customer/store/create-customer.spec.ts b/integration-tests/plugins/__tests__/customer/store/create-customer.spec.ts index 6b986b0bc0..bae4ee4918 100644 --- a/integration-tests/plugins/__tests__/customer/store/create-customer.spec.ts +++ b/integration-tests/plugins/__tests__/customer/store/create-customer.spec.ts @@ -1,11 +1,12 @@ +import { IAuthModuleService, ICustomerModuleService } from "@medusajs/types" +import { initDb, useDb } from "../../../../environment-helpers/use-db" + import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { ICustomerModuleService, IAuthModuleService } from "@medusajs/types" +import adminSeeder from "../../../../helpers/admin-seeder" +import { getContainer } from "../../../../environment-helpers/use-container" import path from "path" import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app" import { useApi } from "../../../../environment-helpers/use-api" -import { getContainer } from "../../../../environment-helpers/use-container" -import { initDb, useDb } from "../../../../environment-helpers/use-db" -import adminSeeder from "../../../../helpers/admin-seeder" jest.setTimeout(50000) @@ -49,6 +50,7 @@ describe("POST /store/customers", () => { const authUser = await authService.createAuthUser({ entity_id: "store_user", provider_id: "test", + scope: "store", }) const jwt = await authService.generateJwtToken(authUser.id, "store") diff --git a/integration-tests/plugins/helpers/create-authenticated-customer.ts b/integration-tests/plugins/helpers/create-authenticated-customer.ts index 54f19cf097..3bb092d950 100644 --- a/integration-tests/plugins/helpers/create-authenticated-customer.ts +++ b/integration-tests/plugins/helpers/create-authenticated-customer.ts @@ -13,6 +13,7 @@ export const createAuthenticatedCustomer = async ( const authUser = await authService.createAuthUser({ entity_id: "store_user", provider_id: "test", + scope: "store", app_metadata: { customer_id: customer.id }, }) diff --git a/packages/auth/integration-tests/__fixtures__/auth-user/index.ts b/packages/auth/integration-tests/__fixtures__/auth-user/index.ts index 46e745ffdc..fefbdc9a6d 100644 --- a/packages/auth/integration-tests/__fixtures__/auth-user/index.ts +++ b/packages/auth/integration-tests/__fixtures__/auth-user/index.ts @@ -1,5 +1,5 @@ -import { SqlEntityManager } from "@mikro-orm/postgresql" import { AuthUser } from "@models" +import { SqlEntityManager } from "@mikro-orm/postgresql" export async function createAuthUsers( manager: SqlEntityManager, @@ -8,15 +8,18 @@ export async function createAuthUsers( 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/auth/integration-tests/__tests__/services/auth-user/index.spec.ts b/packages/auth/integration-tests/__tests__/services/auth-user/index.spec.ts index 07f7aa2426..1737aa19b4 100644 --- a/packages/auth/integration-tests/__tests__/services/auth-user/index.spec.ts +++ b/packages/auth/integration-tests/__tests__/services/auth-user/index.spec.ts @@ -1,12 +1,11 @@ -import { SqlEntityManager } from "@mikro-orm/postgresql" import { AuthUserService } from "@services" - +import ContainerLoader from "../../../../src/loaders/container" import { MikroOrmWrapper } from "../../../utils" +import { SqlEntityManager } from "@mikro-orm/postgresql" +import { asValue } from "awilix" import { createAuthProviders } from "../../../__fixtures__/auth-provider" import { createAuthUsers } from "../../../__fixtures__/auth-user" import { createMedusaContainer } from "@medusajs/utils" -import { asValue } from "awilix" -import ContainerLoader from "../../../../src/loaders/container" jest.setTimeout(30000) @@ -229,7 +228,8 @@ describe("AuthUser Service", () => { { id: "test", provider_id: "manual", - entity_id: "test" + entity_id: "test", + scope: "store" }, ]) diff --git a/packages/auth/integration-tests/__tests__/services/module/auth-user.spec.ts b/packages/auth/integration-tests/__tests__/services/module/auth-user.spec.ts index 86fde3c04b..641ee17e1d 100644 --- a/packages/auth/integration-tests/__tests__/services/module/auth-user.spec.ts +++ b/packages/auth/integration-tests/__tests__/services/module/auth-user.spec.ts @@ -246,6 +246,7 @@ describe("AuthModuleService - AuthUser", () => { id: "test", provider_id: "manual", entity_id: "test", + scope: "store", }, ]) diff --git a/packages/auth/integration-tests/__tests__/services/module/providers.spec.ts b/packages/auth/integration-tests/__tests__/services/module/providers.spec.ts index 556db59838..f3b79046f4 100644 --- a/packages/auth/integration-tests/__tests__/services/module/providers.spec.ts +++ b/packages/auth/integration-tests/__tests__/services/module/providers.spec.ts @@ -83,7 +83,7 @@ describe("AuthModuleService - AuthProvider", () => { const { success, error } = await service.authenticate( "emailpass", { - scope: "non-existing", + authScope: "non-existing", } as any ) diff --git a/packages/auth/integration-tests/__tests__/services/providers/username-password.spec.ts b/packages/auth/integration-tests/__tests__/services/providers/username-password.spec.ts index 3cd76abcd9..7e3955d71b 100644 --- a/packages/auth/integration-tests/__tests__/services/providers/username-password.spec.ts +++ b/packages/auth/integration-tests/__tests__/services/providers/username-password.spec.ts @@ -1,6 +1,6 @@ -import { AuthenticationInput, IAuthModuleService } from "@medusajs/types" import { MedusaModule, Modules } from "@medusajs/modules-sdk" +import { IAuthModuleService } from "@medusajs/types" import { MikroOrmWrapper } from "../../../utils" import Scrypt from "scrypt-kdf" import { SqlEntityManager } from "@mikro-orm/postgresql" @@ -62,6 +62,7 @@ describe("AuthModuleService - AuthProvider", () => { { provider: "emailpass", entity_id: email, + scope: "store", provider_metadata: { password: passwordHash, }, @@ -73,8 +74,8 @@ describe("AuthModuleService - AuthProvider", () => { email: "test@test.com", password: password, }, - scope: "store", - }) + authScope: "store", + } as any) expect(res).toEqual({ success: true, @@ -92,8 +93,8 @@ describe("AuthModuleService - AuthProvider", () => { const res = await service.authenticate("emailpass", { body: { email: "test@test.com" }, - scope: "store", - }) + authScope: "store", + } as any) expect(res).toEqual({ success: false, @@ -106,8 +107,8 @@ describe("AuthModuleService - AuthProvider", () => { const res = await service.authenticate("emailpass", { body: { password: "supersecret" }, - scope: "store", - }) + authScope: "store", + } as any) expect(res).toEqual({ success: false, @@ -127,6 +128,7 @@ describe("AuthModuleService - AuthProvider", () => { // Add authenticated user { provider: "emailpass", + scope: "store", entity_id: email, provider_metadata: { password_hash: passwordHash, @@ -139,8 +141,8 @@ describe("AuthModuleService - AuthProvider", () => { email: "test@test.com", password: "password", }, - scope: "store", - }) + authScope: "store", + } as any) expect(res).toEqual({ success: false, diff --git a/packages/auth/package.json b/packages/auth/package.json index ac68f58918..4516db7edd 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -55,7 +55,7 @@ "@mikro-orm/migrations": "5.9.7", "@mikro-orm/postgresql": "5.9.7", "awilix": "^8.0.0", - "dotenv": "^16.1.4", + "dotenv": "16.3.1", "jsonwebtoken": "^9.0.2", "knex": "2.4.2", "scrypt-kdf": "^2.0.1", diff --git a/packages/auth/src/migrations/.snapshot-medusa-authentication.json b/packages/auth/src/migrations/.snapshot-medusa-auth.json similarity index 91% rename from packages/auth/src/migrations/.snapshot-medusa-authentication.json rename to packages/auth/src/migrations/.snapshot-medusa-auth.json index a31fe796e7..294b0a9ba5 100644 --- a/packages/auth/src/migrations/.snapshot-medusa-authentication.json +++ b/packages/auth/src/migrations/.snapshot-medusa-auth.json @@ -24,20 +24,14 @@ "nullable": false, "mappedType": "text" }, - "domain": { - "name": "domain", + "scope": { + "name": "scope", "type": "text", "unsigned": false, "autoincrement": false, "primary": false, - "nullable": false, - "default": "'all'", - "enumItems": [ - "all", - "store", - "admin" - ], - "mappedType": "enum" + "nullable": true, + "mappedType": "text" }, "config": { "name": "config", @@ -104,6 +98,15 @@ "nullable": true, "mappedType": "text" }, + "scope": { + "name": "scope", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, "user_metadata": { "name": "user_metadata", "type": "jsonb", @@ -119,7 +122,7 @@ "unsigned": false, "autoincrement": false, "primary": false, - "nullable": true, + "nullable": false, "mappedType": "json" }, "provider_metadata": { @@ -136,9 +139,10 @@ "schema": "public", "indexes": [ { - "keyName": "IDX_auth_user_provider_entity_id", + "keyName": "IDX_auth_user_provider_scope_entity_id", "columnNames": [ "provider_id", + "scope", "entity_id" ], "composite": true, diff --git a/packages/auth/src/migrations/Migration20240122041959.ts b/packages/auth/src/migrations/Migration20240122041959.ts deleted file mode 100644 index 15f1526572..0000000000 --- a/packages/auth/src/migrations/Migration20240122041959.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Migration } from "@mikro-orm/migrations" - -export class Migration20240122041959 extends Migration { - async up(): Promise { - this.addSql( - 'create table if not exists "auth_provider" ("provider" text not null, "name" text not null, "domain" text check ("domain" in (\'all\', \'store\', \'admin\')) not null default \'all\', "config" jsonb null, "is_active" boolean not null default false, constraint "auth_provider_pkey" primary key ("provider"));' - ) - - this.addSql( - 'create table if not exists "auth_user" ("id" text not null, "entity_id" text not null, "provider_id" text null, "user_metadata" jsonb null, "app_metadata" jsonb null, "provider_metadata" jsonb null, constraint "auth_user_pkey" primary key ("id"));' - ) - this.addSql( - 'alter table "auth_user" add constraint "IDX_auth_user_provider_entity_id" unique ("provider_id", "entity_id");' - ) - - this.addSql( - 'alter table "auth_user" add constraint "auth_user_provider_id_foreign" foreign key ("provider_id") references "auth_provider" ("provider") on delete cascade;' - ) - } - - async down(): Promise { - this.addSql( - 'alter table "auth_user" drop constraint if exists "auth_user_provider_id_foreign";' - ) - - this.addSql('drop table if exists "auth_provider" cascade;') - - this.addSql('drop table if exists "auth_user" cascade;') - } -} diff --git a/packages/auth/src/migrations/Migration20240201100135.ts b/packages/auth/src/migrations/Migration20240201100135.ts new file mode 100644 index 0000000000..de100203d9 --- /dev/null +++ b/packages/auth/src/migrations/Migration20240201100135.ts @@ -0,0 +1,22 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20240201100135 extends Migration { + + async up(): Promise { + this.addSql('create table "auth_provider" ("provider" text not null, "name" text not null, "scope" text null, "config" jsonb null, "is_active" boolean not null default false, constraint "auth_provider_pkey" primary key ("provider"));'); + + this.addSql('create table "auth_user" ("id" text not null, "entity_id" text not null, "provider_id" text null, "scope" text not null, "user_metadata" jsonb null, "app_metadata" jsonb not null, "provider_metadata" jsonb null, constraint "auth_user_pkey" primary key ("id"));'); + this.addSql('alter table "auth_user" add constraint "IDX_auth_user_provider_scope_entity_id" unique ("provider_id", "scope", "entity_id");'); + + this.addSql('alter table "auth_user" add constraint "auth_user_provider_id_foreign" foreign key ("provider_id") references "auth_provider" ("provider") on delete cascade;'); + } + + async down(): Promise { + this.addSql('alter table "auth_user" drop constraint "auth_user_provider_id_foreign";'); + + this.addSql('drop table if exists "auth_provider" cascade;'); + + this.addSql('drop table if exists "auth_user" cascade;'); + } + +} diff --git a/packages/auth/src/models/auth-provider.ts b/packages/auth/src/models/auth-provider.ts index 0827186069..39ba518235 100644 --- a/packages/auth/src/models/auth-provider.ts +++ b/packages/auth/src/models/auth-provider.ts @@ -20,8 +20,8 @@ export default class AuthProvider { @Property({ columnType: "text" }) name: string - @Enum({ items: () => ProviderDomain, default: ProviderDomain.ALL }) - domain: ProviderDomain = ProviderDomain.ALL + @Property({ columnType: "text", nullable: true }) + scope: string @Property({ columnType: "jsonb", nullable: true }) config: Record | null = null diff --git a/packages/auth/src/models/auth-user.ts b/packages/auth/src/models/auth-user.ts index 0c10053175..122462c26c 100644 --- a/packages/auth/src/models/auth-user.ts +++ b/packages/auth/src/models/auth-user.ts @@ -17,7 +17,10 @@ import { generateEntityId } from "@medusajs/utils" type OptionalFields = "provider_metadata" | "app_metadata" | "user_metadata" @Entity() -@Unique({ properties: ["provider","entity_id" ], name: "IDX_auth_user_provider_entity_id" }) +@Unique({ + properties: ["provider", "scope", "entity_id"], + name: "IDX_auth_user_provider_scope_entity_id", +}) export default class AuthUser { [OptionalProps]: OptionalFields @@ -34,14 +37,17 @@ export default class AuthUser { }) provider: AuthProvider + @Property({ columnType: "text" }) + scope: string + @Property({ columnType: "jsonb", nullable: true }) user_metadata: Record | null - @Property({ columnType: "jsonb", nullable: true }) - app_metadata: Record | null + @Property({ columnType: "jsonb" }) + app_metadata: Record = {} @Property({ columnType: "jsonb", nullable: true }) - provider_metadata: Record | null + provider_metadata: Record | null = null @BeforeCreate() onCreate() { diff --git a/packages/auth/src/providers/email-password.ts b/packages/auth/src/providers/email-password.ts index 3e9760f991..da2617ef2a 100644 --- a/packages/auth/src/providers/email-password.ts +++ b/packages/auth/src/providers/email-password.ts @@ -1,4 +1,8 @@ -import { AbstractAuthModuleProvider, isString } from "@medusajs/utils" +import { + AbstractAuthModuleProvider, + MedusaError, + isString, +} from "@medusajs/utils" import { AuthenticationInput, AuthenticationResponse } from "@medusajs/types" import { AuthUserService } from "@services" @@ -16,6 +20,17 @@ class EmailPasswordProvider extends AbstractAuthModuleProvider { this.authUserSerivce_ = authUserService } + private getHashConfig(scope: string) { + const scopeConfig = this.scopes_[scope].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 { @@ -34,11 +49,38 @@ class EmailPasswordProvider extends AbstractAuthModuleProvider { error: "Email should be a string", } } + let authUser - const authUser = await this.authUserSerivce_.retrieveByProviderAndEntityId( - email, - EmailPasswordProvider.PROVIDER - ) + try { + authUser = await this.authUserSerivce_.retrieveByProviderAndEntityId( + email, + EmailPasswordProvider.PROVIDER + ) + } catch (error) { + if (error.type === MedusaError.Types.NOT_FOUND) { + const password_hash = await Scrypt.kdf( + password, + this.getHashConfig(userData.authScope) + ) + + const [createdAuthUser] = await this.authUserSerivce_.create([ + { + entity_id: email, + provider: EmailPasswordProvider.PROVIDER, + scope: userData.authScope, + provider_metadata: { + password: password_hash.toString("base64"), + }, + }, + ]) + + return { + success: true, + authUser: JSON.parse(JSON.stringify(createdAuthUser)), + } + } + return { success: false, error: error.message } + } const password_hash = authUser.provider_metadata?.password diff --git a/packages/auth/src/providers/google.ts b/packages/auth/src/providers/google.ts index 5a94eb9922..b5558216e1 100644 --- a/packages/auth/src/providers/google.ts +++ b/packages/auth/src/providers/google.ts @@ -1,7 +1,4 @@ -import { - AbstractAuthModuleProvider, - MedusaError, -} from "@medusajs/utils" +import { AbstractAuthModuleProvider, MedusaError } from "@medusajs/utils" import { AuthProviderScope, AuthenticationInput, @@ -9,6 +6,7 @@ import { } from "@medusajs/types" import { AuthProviderService, AuthUserService } from "@services" import jwt, { JwtPayload } from "jsonwebtoken" + import { AuthorizationCode } from "simple-oauth2" import url from "url" @@ -78,7 +76,7 @@ class GoogleProvider extends AbstractAuthModuleProvider { const code = req.query?.code ?? req.body?.code - return await this.validateCallbackToken(code, req.scope, config) + return await this.validateCallbackToken(code, req.authScope, config) } // abstractable @@ -97,14 +95,15 @@ class GoogleProvider extends AbstractAuthModuleProvider { ) } catch (error) { if (error.type === MedusaError.Types.NOT_FOUND) { - authUser = await this.authUserSerivce_.create([ + const [createdAuthUser] = await this.authUserSerivce_.create([ { entity_id, - provider_id: GoogleProvider.PROVIDER, + provider: GoogleProvider.PROVIDER, user_metadata: jwtData!.payload, - app_metadata: { scope }, + scope, }, ]) + authUser = createdAuthUser } else { return { success: false, error: error.message } } @@ -135,24 +134,20 @@ class GoogleProvider extends AbstractAuthModuleProvider { } } - private getConfigFromScope(config: AuthProviderScope): ProviderConfig { - const providerConfig: Partial = {} + private getConfigFromScope( + config: AuthProviderScope & Partial + ): ProviderConfig { + const providerConfig: Partial = { ...config } - if (config.clientId) { - providerConfig.clientID = config.clientId - } else { + if (!providerConfig.clientID) { throw new Error("Google clientID is required") } - if (config.clientSecret) { - providerConfig.clientSecret = config.clientSecret - } else { + if (!providerConfig.clientSecret) { throw new Error("Google clientSecret is required") } - if (config.callbackURL) { - providerConfig.callbackURL = config.callbackUrl - } else { + if (!providerConfig.callbackURL) { throw new Error("Google callbackUrl is required") } @@ -160,9 +155,8 @@ class GoogleProvider extends AbstractAuthModuleProvider { } private originalURL(req: AuthenticationInput) { - const tls = req.connection.encrypted const host = req.headers.host - const protocol = tls ? "https" : "http" + const protocol = req.protocol const path = req.url || "" return protocol + "://" + host + path @@ -173,7 +167,7 @@ class GoogleProvider extends AbstractAuthModuleProvider { ): Promise { await this.authProviderService_.retrieve(GoogleProvider.PROVIDER) - const scopeConfig = this.scopes_[req.scope] + const scopeConfig = this.scopes_[req.authScope] const config = this.getConfigFromScope(scopeConfig) diff --git a/packages/auth/src/services/auth-module.ts b/packages/auth/src/services/auth-module.ts index 3310c28c1f..34ac05f0dd 100644 --- a/packages/auth/src/services/auth-module.ts +++ b/packages/auth/src/services/auth-module.ts @@ -395,7 +395,7 @@ export default class AuthModuleService< protected getRegisteredAuthenticationProvider( provider: string, - { scope }: AuthenticationInput + { authScope }: AuthenticationInput ): AbstractAuthModuleProvider { let containerProvider: AbstractAuthModuleProvider try { @@ -407,7 +407,7 @@ export default class AuthModuleService< ) } - containerProvider.validateScope(scope) + containerProvider.validateScope(authScope) return containerProvider } diff --git a/packages/auth/src/types/services/auth-provider.ts b/packages/auth/src/types/services/auth-provider.ts index 8ef5d9b3b9..dc400222e4 100644 --- a/packages/auth/src/types/services/auth-provider.ts +++ b/packages/auth/src/types/services/auth-provider.ts @@ -1,7 +1,7 @@ export type AuthProviderDTO = { provider: string name: string - domain: ProviderDomain + scope: string is_active: boolean config: Record } @@ -9,7 +9,7 @@ export type AuthProviderDTO = { export type CreateAuthProviderDTO = { provider: string name: string - domain?: ProviderDomain + scope?: string is_active?: boolean config?: Record } @@ -17,15 +17,8 @@ export type CreateAuthProviderDTO = { export type UpdateAuthProviderDTO = { provider: string name?: string - domain?: ProviderDomain is_active?: boolean config?: Record } -export enum ProviderDomain { - ALL = "all", - STORE = "store", - ADMIN = "admin", -} - export type FilterableAuthProviderProps = {} diff --git a/packages/auth/src/types/services/auth-user.ts b/packages/auth/src/types/services/auth-user.ts index c059e980f8..bab3e0c2e4 100644 --- a/packages/auth/src/types/services/auth-user.ts +++ b/packages/auth/src/types/services/auth-user.ts @@ -4,6 +4,7 @@ export type AuthUserDTO = { id: string provider_id: string entity_id: string + scope: string provider: AuthProviderDTO provider_metadata?: Record user_metadata: Record @@ -12,7 +13,8 @@ export type AuthUserDTO = { export type CreateAuthUserDTO = { entity_id: string - provider_id: string + provider: string + scope: string provider_metadata?: Record user_metadata?: Record app_metadata?: Record diff --git a/packages/customer/package.json b/packages/customer/package.json index 6f58a6cf6c..637c791163 100644 --- a/packages/customer/package.json +++ b/packages/customer/package.json @@ -55,7 +55,7 @@ "@mikro-orm/migrations": "5.9.7", "@mikro-orm/postgresql": "5.9.7", "awilix": "^8.0.0", - "dotenv": "^16.1.4", + "dotenv": "16.3.1", "knex": "2.4.2" } } diff --git a/packages/medusa/src/api-v2/auth/[scope]/[authProvider]/callback/route.ts b/packages/medusa/src/api-v2/auth/[scope]/[authProvider]/callback/route.ts new file mode 100644 index 0000000000..54066dce4f --- /dev/null +++ b/packages/medusa/src/api-v2/auth/[scope]/[authProvider]/callback/route.ts @@ -0,0 +1,46 @@ +import { AuthenticationInput, IAuthModuleService } from "@medusajs/types" +import { MedusaRequest, MedusaResponse } from "../../../../../types/routing" + +import { MedusaError } from "@medusajs/utils" +import { ModuleRegistrationName } from "@medusajs/modules-sdk" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const { scope, authProvider } = req.params + + const service: IAuthModuleService = req.scope.resolve( + ModuleRegistrationName.AUTH + ) + + const authData = { + url: req.url, + headers: req.headers, + query: req.query, + body: req.body, + authScope: scope, + protocol: req.protocol, + } as AuthenticationInput + + const authResult = await service.validateCallback(authProvider, authData) + + const { success, error, authUser, location } = authResult + if (location) { + res.redirect(location) + return + } + + if (success) { + req.session.auth_user = authUser + req.session.scope = authUser.scope + + return res.status(200).json({ authUser }) + } + + throw new MedusaError( + MedusaError.Types.UNAUTHORIZED, + error || "Authentication failed" + ) +} + +export const POST = async (req: MedusaRequest, res: MedusaResponse) => { + await GET(req, res) +} diff --git a/packages/medusa/src/api-v2/auth/[scope]/[authProvider]/route.ts b/packages/medusa/src/api-v2/auth/[scope]/[authProvider]/route.ts new file mode 100644 index 0000000000..7d3873a320 --- /dev/null +++ b/packages/medusa/src/api-v2/auth/[scope]/[authProvider]/route.ts @@ -0,0 +1,46 @@ +import { AuthenticationInput, IAuthModuleService } from "@medusajs/types" +import { MedusaRequest, MedusaResponse } from "../../../../types/routing" + +import { MedusaError } from "@medusajs/utils" +import { ModuleRegistrationName } from "@medusajs/modules-sdk" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const { scope, authProvider } = req.params + + const service: IAuthModuleService = req.scope.resolve( + ModuleRegistrationName.AUTH + ) + + const authData = { + url: req.url, + headers: req.headers, + query: req.query, + body: req.body, + authScope: scope, + protocol: req.protocol, + } as AuthenticationInput + + const authResult = await service.authenticate(authProvider, authData) + + const { success, error, authUser, location } = authResult + if (location) { + res.redirect(location) + return + } + + if (success) { + req.session.auth_user = authUser + req.session.scope = authUser.scope + + return res.status(200).json({ authUser }) + } + + throw new MedusaError( + MedusaError.Types.UNAUTHORIZED, + error || "Authentication failed" + ) +} + +export const POST = async (req: MedusaRequest, res: MedusaResponse) => { + await GET(req, res) +} diff --git a/packages/medusa/src/api-v2/store/customers/me/route.ts b/packages/medusa/src/api-v2/store/customers/me/route.ts index 22a9746d97..83b654f84c 100644 --- a/packages/medusa/src/api-v2/store/customers/me/route.ts +++ b/packages/medusa/src/api-v2/store/customers/me/route.ts @@ -1,8 +1,9 @@ -import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { MedusaRequest, MedusaResponse } from "../../../../types/routing" +import { ModuleRegistrationName } from "@medusajs/modules-sdk" + export const GET = async (req: MedusaRequest, res: MedusaResponse) => { - const id = req.auth_user!.app_metadata.customer_id + const id = req.auth_user!.app_metadata?.customer_id const customerModule = req.scope.resolve(ModuleRegistrationName.CUSTOMER) diff --git a/packages/medusa/src/api-v2/store/customers/middlewares.ts b/packages/medusa/src/api-v2/store/customers/middlewares.ts index c60b5f1ea2..f0bf0e8c28 100644 --- a/packages/medusa/src/api-v2/store/customers/middlewares.ts +++ b/packages/medusa/src/api-v2/store/customers/middlewares.ts @@ -7,9 +7,10 @@ import { StorePostCustomersMeAddressesAddressReq, StoreGetCustomersMeAddressesParams, } from "./validators" -import authenticate from "../../../utils/authenticate-middleware" import * as QueryConfig from "./query-config" +import { authenticate } from "../../../utils/authenticate-middleware" + export const storeCustomerRoutesMiddlewares: MiddlewareRoute[] = [ { method: "ALL", diff --git a/packages/medusa/src/api-v2/store/customers/query-config.ts b/packages/medusa/src/api-v2/store/customers/query-config.ts index 5a54fd3bce..7a503843c6 100644 --- a/packages/medusa/src/api-v2/store/customers/query-config.ts +++ b/packages/medusa/src/api-v2/store/customers/query-config.ts @@ -17,7 +17,7 @@ export const defaultStoreCustomersFields: (keyof CustomerDTO)[] = [ ] export const retrieveTransformQueryConfig = { - defaultFields: defaultStoreCustomersFields, + defaultFields: defaultStoreCustomersFields as string[], defaultRelations: defaultStoreCustomersRelations, allowedRelations: allowedStoreCustomersRelations, isList: false, diff --git a/packages/medusa/src/api-v2/store/customers/route.ts b/packages/medusa/src/api-v2/store/customers/route.ts index b7600fc55d..14fb4f1653 100644 --- a/packages/medusa/src/api-v2/store/customers/route.ts +++ b/packages/medusa/src/api-v2/store/customers/route.ts @@ -1,8 +1,30 @@ import { MedusaRequest, MedusaResponse } from "../../../types/routing" -import { createCustomerAccountWorkflow } from "@medusajs/core-flows" + +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" import { CreateCustomerDTO } from "@medusajs/types" +import { createCustomerAccountWorkflow } from "@medusajs/core-flows" export const POST = async (req: MedusaRequest, res: MedusaResponse) => { + if (req.auth_user?.app_metadata?.customer_id) { + const remoteQuery = req.scope.resolve( + ContainerRegistrationKeys.REMOTE_QUERY + ) + + const query = remoteQueryObjectFromString({ + entryPoint: "customer", + variables: { id: req.auth_user.app_metadata.customer_id }, + fields: [], + }) + const [customer] = await remoteQuery(query) + + res.status(200).json({ customer }) + + return + } + const createCustomers = createCustomerAccountWorkflow(req.scope) const customersData = req.validatedBody as CreateCustomerDTO @@ -10,5 +32,9 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => { input: { customersData, authUserId: req.auth_user!.id }, }) + // Set customer_id on session user if we are in session + if (req.session.auth_user) { + req.session.auth_user.app_metadata.customer_id = result.id + } res.status(200).json({ customer: result }) } diff --git a/packages/medusa/src/types/routing.ts b/packages/medusa/src/types/routing.ts index 8a17b7eee0..d270d7adc8 100644 --- a/packages/medusa/src/types/routing.ts +++ b/packages/medusa/src/types/routing.ts @@ -1,11 +1,13 @@ +import type { Customer, User } from "../models" import type { NextFunction, Request, Response } from "express" -import type { Customer, User } from "../models" +import { AuthUserDTO } from "@medusajs/types" import type { MedusaContainer } from "./global" export interface MedusaRequest extends Request { user?: (User | Customer) & { customer_id?: string; userId?: string } scope: MedusaContainer + session?: any requestId?: string auth_user?: { id: string; app_metadata: Record; scope: string } } diff --git a/packages/medusa/src/utils/authenticate-middleware.ts b/packages/medusa/src/utils/authenticate-middleware.ts index 0417017ef4..ffc2326a09 100644 --- a/packages/medusa/src/utils/authenticate-middleware.ts +++ b/packages/medusa/src/utils/authenticate-middleware.ts @@ -1,22 +1,20 @@ -import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { AuthUserDTO, IAuthModuleService } from "@medusajs/types" -import { NextFunction, RequestHandler } from "express" import { MedusaRequest, MedusaResponse } from "../types/routing" +import { NextFunction, RequestHandler } from "express" + +import { ModuleRegistrationName } from "@medusajs/modules-sdk" const SESSION_AUTH = "session" const BEARER_AUTH = "bearer" type MedusaSession = { - auth: { - [authScope: string]: { - user_id: string - } - } + auth_user: AuthUserDTO + scope: string } type AuthType = "session" | "bearer" -export default ( +export const authenticate = ( authScope: string, authType: AuthType | AuthType[], options: { allowUnauthenticated?: boolean } = {} @@ -36,19 +34,18 @@ export default ( let authUser: AuthUserDTO | null = null if (authTypes.includes(SESSION_AUTH)) { - if (session.auth && session.auth[authScope]) { - authUser = await authModule - .retrieveAuthUser(session.auth[authScope].user_id) - .catch(() => null) + if (session.auth_user && session.scope === authScope) { + authUser = session.auth_user } } - if (authTypes.includes(BEARER_AUTH)) { + if (!authUser && authTypes.includes(BEARER_AUTH)) { const authHeader = req.headers.authorization if (authHeader) { const re = /(\S+)\s+(\S+)/ const matches = authHeader.match(re) + // TODO: figure out how to obtain token (and store correct data in token) if (matches) { const tokenType = matches[1] const token = matches[2] diff --git a/packages/types/src/auth/common/auth-provider.ts b/packages/types/src/auth/common/auth-provider.ts index e365364213..3f4122d42f 100644 --- a/packages/types/src/auth/common/auth-provider.ts +++ b/packages/types/src/auth/common/auth-provider.ts @@ -3,7 +3,7 @@ import { BaseFilterable } from "../../dal" export type AuthProviderDTO = { provider: string name: string - domain: ProviderDomain + scope?: string is_active: boolean config: Record | null } @@ -11,29 +11,22 @@ export type AuthProviderDTO = { export type CreateAuthProviderDTO = { provider: string name: string - domain?: ProviderDomain + scope?: string is_active?: boolean - config?: Record + config?: Record } export type UpdateAuthProviderDTO = { provider: string name?: string - domain?: ProviderDomain is_active?: boolean config?: Record } -export enum ProviderDomain { - ALL = "all", - STORE = "store", - ADMIN = "admin", -} - export interface FilterableAuthProviderProps extends BaseFilterable { provider?: string[] is_active?: boolean - domain?: ProviderDomain[] + scope?: string[] name?: string[] } diff --git a/packages/types/src/auth/common/auth-user.ts b/packages/types/src/auth/common/auth-user.ts index 11357a10eb..1946e36083 100644 --- a/packages/types/src/auth/common/auth-user.ts +++ b/packages/types/src/auth/common/auth-user.ts @@ -1,10 +1,11 @@ -import { BaseFilterable } from "../../dal" import { AuthProviderDTO } from "./auth-provider" +import { BaseFilterable } from "../../dal" export type AuthUserDTO = { id: string provider_id: string entity_id: string + scope: string provider: AuthProviderDTO provider_metadata?: Record user_metadata: Record @@ -12,8 +13,9 @@ export type AuthUserDTO = { } export type CreateAuthUserDTO = { - provider_id: string + provider: string entity_id: string + scope: string provider_metadata?: Record user_metadata?: Record app_metadata?: Record diff --git a/packages/types/src/auth/common/provider.ts b/packages/types/src/auth/common/provider.ts index 03dfb74b2e..a13f90052d 100644 --- a/packages/types/src/auth/common/provider.ts +++ b/packages/types/src/auth/common/provider.ts @@ -10,13 +10,13 @@ export type AuthModuleProviderConfig = { scopes: Record } -export type AuthProviderScope = { domain?: string } & Record +export type AuthProviderScope = Record export type AuthenticationInput = { - connection: { encrypted: boolean } url: string headers: Record query: Record body: Record - scope: string + authScope: string + protocol: string } diff --git a/yarn.lock b/yarn.lock index 5775225d01..bed468a813 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7902,7 +7902,7 @@ __metadata: "@mikro-orm/postgresql": 5.9.7 awilix: ^8.0.0 cross-env: ^5.2.1 - dotenv: ^16.1.4 + dotenv: 16.3.1 jest: ^29.6.3 jsonwebtoken: ^9.0.2 knex: 2.4.2 @@ -8022,7 +8022,7 @@ __metadata: "@mikro-orm/postgresql": 5.9.7 awilix: ^8.0.0 cross-env: ^5.2.1 - dotenv: ^16.1.4 + dotenv: 16.3.1 jest: ^29.6.3 knex: 2.4.2 medusa-test-utils: ^1.1.40